Importing data from an Excel file into a database is a very common need in business applications — from importing product data and employee data to transaction data. Laravel doesn't have this feature built in, but there is a popular package called Laravel Excel (by Maatwebsite) that is very easy to use and feature-rich. This tutorial will guide you from installation to importing with data validation.
Installing Laravel Excel
Install the package using Composer:
composer require maatwebsite/excel
For Laravel 11, the service provider and facade are registered automatically. For Laravel 10 and below, publish the optional configuration:
php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider" --tag=config
Make sure the following PHP extensions are enabled (they usually are by default):
php_zipphp_xmlphp_gd2
Preparing the Model and Migration
Example use case: importing product data from Excel.
php artisan make:model Product -m
// Migration
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('sku')->unique();
$table->string('name');
$table->string('category')->nullable();
$table->decimal('price', 12, 2);
$table->integer('stock')->default(0);
$table->timestamps();
});
}
php artisan migrate
Creating the Import Class
php artisan make:import ProductsImport --model=Product
Edit app/Imports/ProductsImport.php:
<?php
namespace App\Imports;
use App\Models\Product;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\SkipsOnError;
use Maatwebsite\Excel\Concerns\SkipsErrors;
use Maatwebsite\Excel\Concerns\WithBatchInserts;
use Maatwebsite\Excel\Concerns\WithChunkReading;
class ProductsImport implements
ToModel,
WithHeadingRow,
WithValidation,
SkipsOnError,
WithBatchInserts,
WithChunkReading
{
use SkipsErrors;
// Read the Excel file in chunks of 1000 rows to save memory
public function chunkSize(): int
{
return 1000;
}
// Insert into the DB in batches
public function batchSize(): int
{
return 500;
}
public function model(array $row): ?Product
{
return new Product([
'sku' => trim($row['sku']),
'name' => trim($row['product_name']),
'category' => $row['category'] ?? null,
'price' => (float) $row['price'],
'stock' => (int) $row['stock'],
]);
}
// Per-row validation
public function rules(): array
{
return [
'sku' => 'required|string|max:50',
'product_name' => 'required|string|max:255',
'price' => 'required',
'stock' => 'required|integer|min:0',
];
}
// Custom error messages
public function customValidationMessages(): array
{
return [
'sku.required' => 'The SKU column is required.',
'product_name.required' => 'The product name column is required.',
'price.required' => 'The price column is required.',
];
}
}
Creating the Controller and Route
php artisan make:controller ImportController
<?php
namespace App\Http\Controllers;
use App\Imports\ProductsImport;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
class ImportController extends Controller
{
public function index()
{
return view('import.index');
}
public function store(Request $request)
{
$request->validate([
'file' => 'required|file|mimes:xlsx,xls,csv|max:10240',
]);
$import = new ProductsImport();
Excel::import($import, $request->file('file'));
$failures = $import->failures();
$errors = $import->errors();
if ($failures->isNotEmpty() || ! empty($errors)) {
$errorMessages = [];
foreach ($failures as $failure) {
$errorMessages[] = "Row {$failure->row()}: " . implode(', ', $failure->errors());
}
return back()->with('import_errors', $errorMessages)
->with('warning', 'Import finished with some errors.');
}
return back()->with('success', 'Data imported successfully!');
}
}
// routes/web.php
use App\Http\Controllers\ImportController;
Route::get('/import', [ImportController::class, 'index'])->name('import.index');
Route::post('/import', [ImportController::class, 'store'])->name('import.store');
Creating the Upload Form View
<!-- resources/views/import/index.blade.php -->
@if(session('success'))
<div style="color:green">{{ session('success') }}</div>
@endif
@if(session('warning'))
<div style="color:orange">{{ session('warning') }}</div>
<ul>
@foreach(session('import_errors', []) as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
<form action="{{ route('import.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<p>File format: .xlsx or .csv</p>
<p>Required columns: sku, product_name, category, price, stock</p>
<input type="file" name="file" accept=".xlsx,.xls,.csv">
@error('file') <p style="color:red">{{ $message }}</p> @enderror
<button type="submit">Import Now</button>
</form>
Excel Template Format
Make sure the uploaded Excel file has the first row as the header (because we use WithHeadingRow). Example column structure:
- Column A:
sku - Column B:
product_name - Column C:
category - Column D:
price - Column E:
stock
Laravel Excel automatically converts the header to lowercase and replaces spaces with underscores.
Conclusion
With the Laravel Excel package, importing data from an Excel file becomes very easy and structured. Use WithChunkReading and WithBatchInserts for large files so you don't run into memory exhaustion. Always include validation and error handling so the user gets clear information if any rows have problems. You can also extend this feature by adding data export using the same class from Laravel Excel.