Excel and CSV import/export are common requirements for any admin panel — exporting a report for someone to open in a spreadsheet, or importing a bulk list of records someone else prepared — and maatwebsite/excel is the standard Laravel package handling both directions.
Installing the package
composer require maatwebsite/excel
Creating an export class
php artisan make:export ProductsExport --model=Product
class ProductsExport implements FromCollection, WithHeadings
{
public function collection()
{
return Product::select('name', 'price', 'stock')->get();
}
public function headings(): array
{
return ['Name', 'Price', 'Stock'];
}
}
Triggering the download
use Maatwebsite\Excel\Facades\Excel;
Route::get('/products/export', function () {
return Excel::download(new ProductsExport(), 'products.xlsx');
});
Changing the file extension in download() to .csv exports the same data as CSV instead of xlsx — the export class itself doesn't need to change, since the package infers the format from the requested filename.
Exporting a large dataset efficiently with FromQuery
class ProductsExport implements FromQuery
{
public function query()
{
return Product::query()->where('active', true);
}
}
FromQuery (rather than FromCollection) streams results from the database in chunks instead of loading the entire result set into memory at once — the difference that matters specifically once an export grows into the tens of thousands of rows, where FromCollection risks exhausting available memory.
Creating an import class
php artisan make:import ProductsImport --model=Product
class ProductsImport implements ToModel, WithHeadingRow
{
public function model(array $row)
{
return new Product([
'name' => $row['name'],
'price' => $row['price'],
'stock' => $row['stock'],
]);
}
}
WithHeadingRow treats the file's first row as column headers, letting $row be accessed by lowercase header name ($row['name']) rather than a numeric index — considerably more readable and resilient to column reordering than $row[0].
Handling the uploaded file and running the import
public function import(Request $request)
{
$request->validate(['file' => 'required|mimes:xlsx,csv']);
Excel::import(new ProductsImport(), $request->file('file'));
return back()->with('success', 'Products imported.');
}
Validating each row during import
class ProductsImport implements ToModel, WithHeadingRow, WithValidation
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
];
}
}
Rows that fail validation are skipped rather than crashing the entire import — WithValidation collects the failures, which can then be reported back to the user rather than allowing bad data to silently reach the database.