Need to import Excel data in Laravel 12 when column headers vary? Learn how to let users map headers, preview rows, and import data cleanly and safely using Laravel Excel.
When building admin panels, dashboards, or business tools in Laravel, Excel imports are a must-have. Whether you're importing user records, inventory, leads, or transaction logs. Excel is the go-to format for non-technical users.
But we can’t expect end-users to always use the same headers as we do.
You might expect:
Name, Email, Phone
But the uploaded file could have:
Full Name, Mail, Contact Number
If your app expects fixed headers, this will break. Instead, the smart way is to let users map their Excel columns to your database fields, preview the data, and then import it.
In this guide, we'll build a dynamic Excel importer in Laravel 12, complete with:
File upload
Header mapping UI
Preview of first few rows
Final data import
Let’s build a system that adapts to the data, not the other way around.
You’ll need:
Laravel 12 installation
Laravel Excel (maatwebsite/excel
)
Basic setup with Blade, routes, and controllers
Install the package via Composer:
composer require maatwebsite/excel
Optional but helpful (if you want customization):
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"
Create a basic file upload form:
<form action="{{ route('users.import.preview') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="file" accept=".xlsx,.xls,.csv" required>
<button type="submit">Upload & Continue</button>
</form>
In your controller:
use Maatwebsite\Excel\Facades\Excel;
public function ImportPreview(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:xlsx,xls,csv',
]);
$collection = Excel::toCollection(null, $request->file('file'));
$headers = collect($collection[0]->first())->keys();
$storedFile = $request->file('file')->store('temp');
return view('users.import.map', [
'headers' => $headers,
'temp_file' => $storedFile,
]);
}
Then show the mapping form (resources/views/users/import/map.blade.php
):
<form action="{{ route('users.import.preview.rows') }}" method="POST">
@csrf
<input type="hidden" name="temp_file" value="{{ $temp_file }}">
@foreach ($headers as $header)
<label>{{ $header }}</label>
<select name="mapping[{{ $header }}]" required>
<option value="">-- Select Field --</option>
<option value="name">Name</option>
<option value="email">Email</option>
<option value="phone">Phone</option>
</select>
<br>
@endforeach
<button type="submit">Preview Rows</button>
</form>
This lets users map Excel headers to your actual database fields.
In your controller:
public function previewData(Request $request)
{
$request->validate([
'mapping' => 'required|array',
'temp_file' => 'required|string',
]);
$file = storage_path('app/' . $request->temp_file);
$collection = Excel::toCollection(null, $file);
$rows = $collection[0]->slice(1, 10); // Skip heading row
$preview = $rows->map(function ($row) use ($request) {
$data = [];
foreach ($request->mapping as $excelHeader => $field) {
$data[$field] = $row[$excelHeader] ?? null;
}
return $data;
});
return view('users.import.preview-data', [
'preview' => $preview,
'mapping' => $request->mapping,
'temp_file' => $request->temp_file,
]);
}
And show the preview (resources/views/users/import/preview-data.blade.php
):
<form method="POST" action="{{ route('users.import.final') }}">
@csrf
<input type="hidden" name="temp_file" value="{{ $temp_file }}">
@foreach ($mapping as $excel => $field)
<input type="hidden" name="mapping[{{ $excel }}]" value="{{ $field }}">
@endforeach
<table border="1" cellpadding="5">
<thead>
<tr>
@foreach (array_keys($preview->first() ?? []) as $field)
<th>{{ ucfirst($field) }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach ($preview as $row)
<tr>
@foreach ($row as $value)
<td>{{ $value }}</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
<button type="submit">Confirm & Import</button>
</form>
Back in your controller:
use App\Imports\UsersImport;
public function finalImport(Request $request)
{
$request->validate([
'mapping' => 'required|array',
'temp_file' => 'required|string',
]);
$filePath = storage_path('app/' . $request->temp_file);
Excel::import(new UsersImport($request->mapping), $filePath);
return redirect()->route('users.import.form')->with('success', 'Data imported successfully!');
}
use App\Models\User;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class UsersImport implements ToModel, WithHeadingRow
{
protected array $mapping;
public function __construct(array $mapping)
{
$this->mapping = $mapping;
}
public function model(array $row): User
{
return new User([
'name' => $row[$this->getHeader('name')] ?? null,
'email' => $row[$this->getHeader('email')] ?? null,
'phone' => $row[$this->getHeader('phone')] ?? null,
]);
}
protected function getHeader(string $field): ?string
{
return collect($this->mapping)->flip()->get($field);
}
}
You can even expand this logic to support conditional mapping, transformation, and per-field validation.
Use WithValidation
interface to validate each row
Queue large imports using ShouldQueue
+ WithChunkReading
Save and reuse mapping templates
Excel import is a common requirement, but hardcoding column names makes your system fragile. By giving users the power to map Excel headers to database fields, and preview their data before importing, you:
Prevent bad data from going into your system
Build trust with users
Make your Laravel app enterprise-ready
This approach works beautifully for admin dashboards, CRMs, ERP software, and any system dealing with external data.