Tuesday, July 7, 2026Today's Paper

Omni Apps

Laravel Maatwebsite Excel: The Ultimate Guide to Imports & Exports
May 22, 2026 · 14 min read

Laravel Maatwebsite Excel: The Ultimate Guide to Imports & Exports

Master laravel maatwebsite excel imports and exports. Learn how to install, validate, format, and queue large datasets in Laravel 8, 9, 10, and 11.

May 22, 2026 · 14 min read
LaravelPHP DevelopmentData Processing

Introduction

When building modern web applications, handling file uploads and data downloads is a ubiquitous requirement. Whether you are generating monthly financial reports for stakeholders or enabling users to batch-upload system inventories, dealing with spreadsheet files can quickly turn into an engineering bottleneck. Fortunately, the laravel maatwebsite excel package (officially known as Laravel Excel) acts as an elegant, Laravel-flavored wrapper around PhpSpreadsheet. It simplifies exports and imports into a clean, fluent syntax that integrates seamlessly with Eloquent, Laravel's validation engine, and the queue system.

In this comprehensive guide, we will explore everything you need to master maatwebsite excel in laravel, from installation across multiple Laravel versions to advanced formatting, robust import validation, multi-sheet handling, and processing massive multi-million row datasets. Whether you are maintaining legacy systems using maatwebsite excel laravel 7, migrating projects on laravel 8 maatwebsite excel, or working on modern deployments using maatwebsite excel laravel 9 and newer (including Laravel 10 and 11), this tutorial provides the production-ready code you need.


1. Architectural Evolution: Maatwebsite Excel 2.x vs 3.1

Before diving into installation, it is helpful to understand how the architecture of this library has evolved. If you are updating a legacy application or looking up tutorials online, you will frequently find outdated guides written for Laravel Excel 2.x.

In the old 2.x version, operations were highly procedural. Code was typically crammed into controllers using heavy closures, like this:

// Outdated 2.1 syntax - Do not use this in modern projects!
Excel::create('users', function($excel) {
    $excel->sheet('Sheet 1', function($sheet) {
        $sheet->fromArray(User::all()->toArray());
    });
})->download('xlsx');

This old pattern suffered from major issues. It loaded all data directly into memory at once, causing memory leaks on moderate-sized datasets. It was also difficult to unit test and lacked encapsulation.

Laravel Excel 3.x completely reimagined this. Every import or export is now encapsulated in its own clean PHP class, called a Concern-based object. Instead of procedural configurations, you implement interfaces (known in this library as 'Concerns') like FromQuery, WithHeadings, and WithMapping. This decoupled architecture drastically increases code reuse, enables automated performance optimization (like automated chunking), and allows exports and imports to be tested in isolation.


2. Installation & Multi-Version Configuration

Setting up the package is straightforward, but composer dependency constraints can occasionally cause friction depending on which version of PHP and Laravel you are running.

Step-by-Step Installation

To add the package to your project, run the following composer command:

composer require maatwebsite/excel

This command automatically grabs the latest stable release and downloads its underlying engine, phpoffice/phpspreadsheet.

Resolving Dependency Conflicts

If you are working on older systems like maatwebsite excel laravel 7 or upgrading to newer frameworks like Laravel 10/11, you may encounter a 'Your requirements could not be resolved to an installable set of packages' error. This is often caused by mismatched PHP versions or conflicting spreadsheet dependencies. You can resolve this by running:

composer require maatwebsite/excel --with-all-dependencies

Configuration and Service Providers

In older installations (such as maatwebsite excel in laravel 8 or Laravel 7), you might need to register the service provider and facade manually if auto-discovery fails. Open config/app.php and add the following lines:

'providers' => [
    /* ... other providers ... */
    Maatwebsite\Excel\ExcelServiceProvider::class,
],

'aliases' => [
    /* ... other aliases ... */
    'Excel' => Maatwebsite\Excel\Facades\Excel::class,
]

Note: If you are using Laravel 11, provider registration has been simplified. You do not need to edit config/app.php unless you have manually disabled package auto-discovery.

To publish the configuration file to customize formatting, CSV delimiters, or default temporary storage disks, run:

php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider" --tag=config

This creates a config/excel.php file, which allows you to fine-tune your spreadsheet behaviors.


3. Crafting Professional Exports with Custom Formatting

Many tutorials show you how to export a raw, unstyled database table, but in production, clients demand readable, professional, and well-structured reports. We will build a highly customized export class that structures data, maps columns cleanly, styles the headers, and formats cell values.

Step 1: Create the Export Class

Generate a dedicated export file using the Artisan CLI. Separating your export logic from your controller keeps your codebase clean and testable:

php artisan make:export UsersExport --model=User

This command creates a skeleton file in app/Exports/UsersExport.php.

Step 2: Implement Advanced Concerns

By default, the generator creates a class using FromCollection. While this is fine for small tables, loading thousands of Eloquent models directly into memory can easily exceed your server's memory limits. To scale efficiently, we will use the FromQuery concern. We will also implement WithHeadings for header rows, WithMapping to control column order, WithColumnFormatting for cell data formats, and WithStyles to give our sheet a polished look.

Update your UsersExport.php to match the following implementation:

namespace App\Exports;

use App\Models\User;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithStyles;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class UsersExport implements FromQuery, WithHeadings, WithMapping, WithColumnFormatting, WithStyles
{
    /**
    * Pull the query builder instead of fetching all records into memory at once.
    * This allows Laravel Excel to chunk your records automatically under the hood.
    */
    public function query()
    {
        return User::query()->where('is_active', true);
    }

    /**
    * Define the headers of the generated Excel sheet.
    */
    public function headings(): array
    {
        return [
            'ID',
            'Full Name',
            'Email Address',
            'Registration Date',
        ];
    }

    /**
    * Map database columns to specific spreadsheet rows.
    * Use PhpSpreadsheet's helper to convert model dates into Excel-serial dates.
    */
    public function map($user): array
    {
        return [
            $user->id,
            $user->name,
            $user->email,
            Date::dateTimeToExcel($user->created_at),
        ];
    }

    /**
    * Apply correct cell formats. Column 'D' (Registration Date) is formatted as YYYY-MM-DD.
    */
    public function columnFormats(): array
    {
        return [
            'D' => NumberFormat::FORMAT_DATE_YYYYMMDD2,
        ];
    }

    /**
    * Apply elegant styling directly to our worksheet.
    */
    public function styles(Worksheet $sheet)
    {
        return [
            // Row 1 (Header) styled with bold text, larger size, and a light-gray background
            1 => [
                'font' => [
                    'bold' => true, 
                    'size' => 12
                ],
                'fill' => [
                    'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
                    'startColor' => ['rgb' => 'F2F2F2']
                ]
            ],
        ];
    }
}

Step 3: Trigger the Export in Your Controller

Next, create a controller to execute this export and return it as a direct download:

namespace App\Http\Controllers;

use App\Exports\UsersExport;
use Maatwebsite\Excel\Facades\Excel;

class UserController extends Controller
{
    public function export()
    {
        return Excel::download(new UsersExport, 'active_users.xlsx');
    }
}

Add your route to routes/web.php to expose this action:

use App\Http\Controllers\UserController;

Route::get('/users/export', [UserController::class, 'export'])->name('users.export');

When you hit this route, the package compiles the query, streams it through your formatted export file, and initiates a secure browser download.

Writing Dynamic Formulas

To compute calculations dynamically within Excel rather than performing raw PHP math, you can inject formulas into your row mapping array. If you have a column for price, tax rate, and final cost, you can write Excel formulas directly into your cells:

public function map($invoice): array
{
    static $rowNumber = 1;
    $rowNumber++;

    return [
        $invoice->sku,
        $invoice->subtotal,
        $invoice->tax_rate,
        '=B' . $rowNumber . '*C' . $rowNumber // Computes the tax amount dynamically inside Excel
    ];
}

4. High-Performance Imports with Row-by-Row Data Validation

While exporting is relatively simple, executing a laravel maatwebsite excel import safely requires a lot more caution. Standard files provided by users are notorious for having missing values, invalid email formats, and unexpected data structures. If you process imports blindly, you will run into database constraints, duplicate keys, or catastrophic application crashes.

Let us build a robust importer that validates incoming rows, reports errors gracefully, and ignores invalid rows without crashing the entire import script.

Step 1: Create the Import Class

Run the following Artisan command to generate your importer:

php artisan make:import UsersImport --model=User

Step 2: Implement Validation Concerns

We will make the importer incredibly robust by applying:

  1. WithHeadingRow: Allows referencing columns by their textual header instead of numeric array indexes.
  2. WithValidation: Validates each row using standard Laravel validation syntax.
  3. SkipsOnFailure: Collects validation failures gracefully instead of throwing exceptions that stop the import execution.

Here is the production-ready code for app/Imports/UsersImport.php:

namespace App\Imports;

use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
use Maatwebsite\Excel\Concerns\SkipsFailures;
use Maatwebsite\Excel\Concerns\SkipsOnFailure;

class UsersImport implements ToModel, WithHeadingRow, WithValidation, SkipsOnFailure
{
    use SkipsFailures;

    /**
    * Convert each spreadsheet row into a database record.
    * Column names correspond directly to your spreadsheet headers (converted to snake_case).
    */
    public function model(array $row)
    {
        return new User([
            'name'     => $row['name'],
            'email'    => $row['email'],
            'password' => Hash::make($row['password'] ?? 'Temporary123!'),
        ]);
    }

    /**
    * Set validation rules for each row.
    * 'email' checks against standard format and enforces database uniqueness.
    */
    public function rules(): array
    {
        return [
            'name'  => 'required|string|max:100',
            'email' => 'required|email|unique:users,email',
        ];
    }

    /**
    * Custom validation messages (optional, but highly recommended for clear UX).
    */
    public function customValidationMessages()
    {
        return [
            'email.unique' => 'The email address :input is already registered in our system.',
            'name.required' => 'The name field cannot be empty.',
        ];
    }
}

Step 3: Handle Import Failures in Your Controller

When executing this import in your controller, you can catch the collected validation errors and display them on your frontend. This provides high-quality, self-service feedback to your users so they can fix their spreadsheet data and re-upload.

namespace App\Http\Controllers;

use App\Imports\UsersImport;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;

class UserImportController extends Controller
{
    public function import(Request $request)
    {
        // 1. Validate that a file is actually uploaded and is of the right format
        $request->validate([
            'excel_file' => 'required|mimes:xlsx,xls,csv|max:10240', // Limit to 10MB
        ]);

        $import = new UsersImport();

        // 2. Perform the import
        Excel::import($import, $request->file('excel_file'));

        // 3. Inspect validation failures
        if ($import->failures()->isNotEmpty()) {
            return back()->with('import_failures', $import->failures());
        }

        return back()->with('success', 'Excel data successfully imported!');
    }
}

Step 4: Render Failures on Your Frontend

In your Blade template, you can loop through these failures to tell the user exactly where they went wrong:

@if (session()->has('import_failures'))
    <div class="alert alert-danger">
        <h4>Some rows could not be imported:</h4>
        <ul>
            @foreach (session()->get('import_failures') as $validationFailure)
                <li>
                    <strong>Row {{ $validationFailure->row() }}:</strong> 
                    {{ implode(', ', $validationFailure->errors()) }} 
                    (Submitted values: <em>{{ json_encode($validationFailure->values()) }}</em>)
                </li>
            @endforeach
        </ul>
    </div>
@endif

This prevents bad data from corrupting your database, ensures user-friendliness, and keeps your backend secure.


5. Advanced Import Handling: Multi-Sheet Support

Often, a spreadsheet uploaded by a business team contains multiple sheets containing different types of structured data—for example, a sheet for 'Clients' and another for 'Orders'. Importing this manually cell-by-cell would be extremely tedious.

To handle multi-sheet imports cleanly, we can implement the WithMultipleSheets concern. This allows us to route separate sheets to entirely independent importer classes, keeping our codebase clean and strictly single-responsibility.

Creating the Multi-Sheet Router Class

Create a root importer class, such as app/Imports/CompanyDataImport.php:

namespace App\Imports;

use Maatwebsite\Excel\Concerns\WithMultipleSheets;

class CompanyDataImport implements WithMultipleSheets
{
    /**
    * Map sheet names to specific import classes.
    */
    public function sheets(): array
    {
        return [
            'Clients' => new ClientsImport(),
            'Orders'  => new OrdersImport(),
        ];
    }
}

In this setup, ClientsImport and OrdersImport are standard importer files implementing ToModel (and optionally WithValidation and WithHeadingRow). You trigger this import inside your controller exactly the same way:

Excel::import(new CompanyDataImport, $request->file('excel_file'));

This architecture keeps your code incredibly clean and makes handling multi-faceted business files highly manageable.


6. Scaling Imports & Exports for Massive Datasets

When you work with massive spreadsheets—such as an inventory of 200,000 products—processing everything in a single web request will trigger PHP's max_execution_time limits or exhaust the server's PHP memory allocation. To keep your application blazing fast and secure under high load, you must integrate chunking, batch inserts, and background queueing.

Optimizing Imports: Chunking and Batching

To scale imports, we combine WithChunkReading and WithBatchInserts:

  • WithChunkReading: Reads the Excel file in small, manageable pieces (e.g., 500 rows at a time). This keeps the memory usage of the underlying parser extremely low.
  • WithBatchInserts: Batches the Eloquent database queries. Instead of executing 500 individual INSERT queries, Laravel Excel wraps them into a single fast SQL statement.

Modify your import class to implement these performance-oriented interfaces:

namespace App\Imports;

use App\Models\Product;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithChunkReading;
use Maatwebsite\Excel\Concerns\WithBatchInserts;

class ProductsLargeImport implements ToModel, WithChunkReading, WithBatchInserts
{
    public function model(array $row)
    {
        return new Product([
            'sku'   => $row[0],
            'title' => $row[1],
            'price' => $row[2],
        ]);
    }

    /**
    * Determine how many rows to insert in a single SQL query.
    */
    public function batchSize(): int
    {
        return 1000;
    }

    /**
    * Determine how many rows to read into memory from the file at once.
    */
    public function chunkSize(): int
    {
        return 1000;
    }
}

Optimizing Exports: Queueing with Background Jobs

For heavy database queries during exports, do not make your users wait for a loading spinner. Instead, push the process to your Laravel queue worker by implementing the ShouldQueue contract.

namespace App\Exports;

use App\Models\Invoice;
use Illuminate\Contracts\Queue\ShouldQueue;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\Exportable;

class InvoicesLargeExport implements FromQuery, ShouldQueue
{
    use Exportable;

    public function query()
    {
        return Invoice::query()->where('status', 'paid');
    }
}

In your controller, you can initiate the queued export like this:

public function queueExport()
{
    (new InvoicesLargeExport)->store('exports/invoices_report.xlsx', 'public');

    return back()->with('status', 'Your large export has started in the background. We will notify you when it is ready for download!');
}

Behind the scenes, Laravel Excel chunks your query, dispatches multiple background jobs, compiles the final spreadsheet on your server's storage disk, and frees up your user's browser immediately.


7. Troubleshooting Common Pitfalls and FAQs

Why is Composer trying to install version 1.1 instead of 3.x?

This usually happens when your environment's PHP configuration or Laravel version has a conflict with PhpSpreadsheet. Since PhpSpreadsheet requires specific PHP extensions (like ext-zip, ext-xml, ext-gd), Composer fallback patterns might incorrectly suggest ancient versions of the library. Make sure your local and production environments have these dependencies enabled:

  • php_zip
  • php_xml
  • php_gd2
  • php_iconv

Verify this by running php -m on your terminal, then try installing again with the --with-all-dependencies flag.

Why do my imported dates look like random integers (e.g., "44197")?

Excel stores dates internally as sequential serial numbers counting the days since January 1, 1900. When importing, you need to parse this number back into a native PHP DateTime object. You can easily achieve this using PhpSpreadsheet's built-in helper:

$dateTimeObject = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($row['created_at']);

Can I write directly into an existing Excel template?

Yes. If you have a pre-designed spreadsheet with complex formulas, charts, and brand styling, you can load the template, write data to specific coordinates, and download the updated version. This is accomplished using events:

use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\BeforeExport;

class TemplateExport implements WithEvents
{
    public function registerEvents(): array
    {
        return [
            BeforeExport::class => function(BeforeExport $event) {
                $event->writer->reopen(new \Maatwebsite\Excel\Files\LocalTemporaryFile(storage_path('templates/report.xlsx')), \Maatwebsite\Excel\Excel::XLSX);
                $sheet = $event->writer->getSheetByIndex(0);
                $sheet->setCellValue('B2', 'Dynamic Monthly Report');
            },
        ];
    }
}

How do I export simple arrays without Eloquent models?

If you are processing highly customized datasets, API results, or aggregate queries that do not map directly to Eloquent models, use the FromArray concern instead of FromQuery or FromCollection:

namespace App\Exports;

use Maatwebsite\Excel\Concerns\FromArray;

class CustomDataExport implements FromArray
{
    protected $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function array(): array
    {
        return $this->data;
    }
}

Conclusion

The laravel maatwebsite excel library is an indispensable tool in the modern Laravel ecosystem. By shifting from loading full collections to using query-based chunking, enforcing validation logic with user-friendly error views, and pushing massive tasks to background queues, you can deliver a smooth, resilient experience to your users.

Implement these patterns in your next deployment to keep your memory footprint low, your database inputs secure, and your spreadsheets looking visually flawless.

Related articles
Laravel Password Generator: Secure Keys for Your App
Laravel Password Generator: Secure Keys for Your App
Learn how to create a robust Laravel password generator for enhanced application security. Explore best practices and code examples for generating strong, unique passwords.
Jun 10, 2026 · 12 min read
Read →
Laravel Hash Generator: Securely Hash Passwords
Laravel Hash Generator: Securely Hash Passwords
Learn how to use a Laravel hash generator to securely hash passwords for your web applications. Understand best practices and implementation.
Jun 1, 2026 · 13 min read
Read →
CSV to Table Online: Convert and Format Data Instantly
CSV to Table Online: Convert and Format Data Instantly
Convert raw CSV data into beautiful, interactive tables with a free csv to table online converter. Learn formatting, SQL generation, and reverse conversion.
May 24, 2026 · 11 min read
Read →
Laravel Export CSV: The Ultimate Guide to Fast, Memory-Safe Exports
Laravel Export CSV: The Ultimate Guide to Fast, Memory-Safe Exports
Master Laravel export CSV techniques. Learn to build blazing-fast, memory-safe CSV exports using Maatwebsite Laravel Excel and native PHP database streams.
May 23, 2026 · 13 min read
Read →
How to Create a Video Sitemap: Dynamic Laravel & GSC Guide
How to Create a Video Sitemap: Dynamic Laravel & GSC Guide
Learn how to create a video sitemap to boost your video SEO. Master dynamic sitemap generation in Laravel and submit them to Google Search Console easily.
May 23, 2026 · 11 min read
Read →
You May Also Like