Reader Stacks

Importing and Exporting Excel and CSV Data in Laravel

The maatwebsite/excel package handles both directions — exporting a query's results to a downloadable spreadsheet, and reading an uploaded file's rows back into the database.

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.