Search by

mohd-arbaaz / laravel-excel-wrapper

mohd-arbaaz

A unified API for Excel imports and exports in Laravel, supporting both Maatwebsite/Excel and OpenSpout.

Package info

gitlab.com/mohd-arbaaz/laravel-excel-wrapper

Issues

pkg:composer/mohd-arbaaz/laravel-excel-wrapper

Statistics

Installs: 359

Dependents: 0

Suggesters: 0

Stars: 0

v2.2.0 2026-09-27 20:55 UTC

This package is auto-updated.

Last update: 2026-09-27 16:56:35 UTC


README

Latest Version on Packagist Total Downloads License

A unified API for Excel imports and exports in Laravel, supporting both Maatwebsite/Excel (rich features) and OpenSpout (high performance).

Features

  • ๐ŸŽฏ Unified Column Definition - Define columns once using ColumnDef data objects
  • ๐Ÿ“ Typed Cells - Real dates and numbers with Excel number formats, so files sort, filter and sum
  • ๐Ÿ”„ Two Drivers - Choose between Maatwebsite/Excel (styling, formulas) or OpenSpout (speed, memory efficiency)
  • โœ… Built-in Validation - Laravel validation rules integrated into imports
  • ๐Ÿ“Š Multi-Sheet Support - Export and import multiple sheets in a single file
  • ๐ŸŽจ Conditional Formatting - Color coding based on values (Maatwebsite only)
  • ๐Ÿ“ฆ Chunked Processing - Handle large files efficiently
  • ๐Ÿ”ง Type Transformations - Automatic formatting for dates, booleans, amounts, etc.
  • ๐Ÿงพ Blade Sheets - Render a sheet from a Blade template for document-shaped output
  • ๐Ÿงฎ Sheet Polish - Totals row, title rows, freeze panes, autofilter and autosize, all opt-in

Installation

composer require mohd-arbaaz/laravel-excel-wrapper

Both drivers - maatwebsite/excel (rich features, styling) and openspout/openspout (high performance, low memory) - are installed with the package; no extra step is needed.

Publish Configuration (Optional)

php artisan vendor:publish --tag=excel-wrapper-config

Quick Start

Export Example

use ExcelWrapper\Abstracts\LaravelExcelExport;
use ExcelWrapper\Data\ColumnDef;
use Illuminate\Support\Collection;

class UsersExport extends LaravelExcelExport
{
    public function columnConfig(): Collection
    {
        return collect([
            ColumnDef::make('name', 'Full Name')->string()->width(25),
            ColumnDef::make('email', 'Email Address')->string()->width(30),
            ColumnDef::make('balance', 'Account Balance')
                ->amount()
                ->colored()
                ->summable()
                ->width(15),
            ColumnDef::make('created_at', 'Joined')
                ->date()
                ->width(15),
        ]);
    }

    public function collection(): ?Collection
    {
        return User::all();
    }

    protected function showTotals(): bool
    {
        return true;
    }

    protected function freezeHeader(): bool
    {
        return true;
    }
}

// Usage
return (new UsersExport)->download('users.xlsx');

Import Example

use ExcelWrapper\Abstracts\LaravelExcelImport;
use ExcelWrapper\Data\ColumnDef;
use Illuminate\Support\Collection;

class UsersImport extends LaravelExcelImport
{
    public function columnConfig(): Collection
    {
        return collect([
            ColumnDef::make('name', 'Name')
                ->string()
                ->rules('required|string|max:255'),
            ColumnDef::make('email', 'Email')
                ->string()
                ->alias('Email Address')
                ->rules('required|email|unique:users,email'),
            ColumnDef::make('balance', 'Balance')
                ->amount()
                ->rules('nullable|numeric'),
        ]);
    }

    public function onRow(array $row): void
    {
        User::create($row);
    }
}

// Usage
$result = (new UsersImport)->import('users.xlsx');

if ($result->hasErrors()) {
    foreach ($result->errors as $error) {
        echo "Row {$error['row']}: {$error['field']} - {$error['messages'][0]}";
    }
}

Reading exact Excel row numbers

OpenSpout drops blank rows by default, so row numbers after the first blank row drift. preserveEmptyRows() keeps them for counting, so every row number is the one the user sees in Excel. The other reader options below are opt-in too (OpenSpout driver):

use ExcelWrapper\Abstracts\OpenSpoutImport;
use ExcelWrapper\Data\HeaderRow;
use ExcelWrapper\Data\ImportRow;
use Illuminate\Support\Collection;

class ParcelsImport extends OpenSpoutImport
{
    public function columnConfig(): Collection { /* ... */ }

    // Sees the raw header row before it is mapped. Return false to refuse
    // the file; the result looks like a failed required-column check.
    protected function onHeader(HeaderRow $header): bool
    {
        foreach ($header->duplicates() as $name) {
            $this->errors[] = ['row' => $header->number, 'field' => $name,
                'messages' => ["Column '{$name}' appears twice."], 'value' => null];
            $this->errorCount++;
        }

        return $header->duplicates() === [];
    }

    // Called for every valid row, with its Excel row number.
    public function onRowNumbered(ImportRow $row): void
    {
        Parcel::create($row->values + ['source_row' => $row->number]);

        if ($row->get('weight_g') > 30_000) {
            $this->stopReading(); // stop once this row is done
        }
    }
}

$result = (new ParcelsImport)
    ->preserveEmptyRows()   // row numbers match Excel; blank rows are skipped
    ->maxRows(5000)         // stop at the 5,001st data row without reading on
    ->readsFormulaValues()  // =B2*C2 reads as the value saved in the file
    ->import('parcels.xlsx');

if ($result->stoppedEarly()) {
    echo "Too many rows: stopped at row {$result->stoppedAtRow}.";
}

onRow(array $row) keeps working. onRowNumbered() calls it by default, so override whichever you need. Formulas are never evaluated. A formula saved without a value reads as 0.0, but Excel and LibreOffice always save one. See the OpenSpout guide for details.

Exporting user input safely

OpenSpout writes any string that starts with = as a formula. If an export echoes user input, opt in to plain text:

class CommentsExport extends OpenSpoutExport
{
    protected function writesStringsAsText(): bool { return true; }  // never a formula
    protected function escapesFormulaText(): bool { return true; }   // '=, '+, '-, '@ ...
}

Formula columns (ColumnDef::formula()) still write formulas. For template workbooks, the Laravel Excel driver adds ColumnDef::dropdown(), hidden() sheets, namedRanges() and selectedCell(). See the Laravel Excel guide.

Choosing a Driver

FeatureLaravel ExcelOpenSpout
Stylingโœ… Full supportโŒ Limited
Conditional Formattingโœ… YesโŒ No
Formulasโœ… Yesโœ… Basic
Memory UsageHigherโœ… Low
Speed (large files)Slowerโœ… Fast
Multi-sheetโœ… Yesโœ… Yes

Use Laravel Excel when:

  • You need styling, conditional formatting, or complex formulas
  • File sizes are moderate (< 50k rows)
  • You need Excel-specific features

Use OpenSpout when:

  • Processing large files (100k+ rows)
  • Memory is constrained
  • You don't need advanced styling

Switching Drivers

Simply change the base class - the columnConfig() and data methods remain the same:

// Laravel Excel
use ExcelWrapper\Abstracts\LaravelExcelExport;
class UsersExport extends LaravelExcelExport { ... }

// OpenSpout
use ExcelWrapper\Abstracts\OpenSpoutExport;
class UsersExport extends OpenSpoutExport { ... }

Documentation

Requirements

  • PHP 8.4+
  • Laravel 12 or 13
  • spatie/laravel-data ^4.23

Installed with the package:

  • maatwebsite/excel ^4.0.2 (Laravel Excel driver)
  • openspout/openspout ^5.1 (OpenSpout driver)

Staying on PHP 8.2/8.3, maatwebsite/excel 3.x or openspout 4.x? Use the 1.x branch (Laravel 12). Laravel 11 is not supported by either line. The floors above come from the drivers: maatwebsite/excel 4 requires Laravel 12+, and openspout 5 requires PHP 8.4+.

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Upgrading

Upgrading from 1.x? See UPGRADING.md.

Contributing

Please see CONTRIBUTING.md for branch layout, test conventions and the constraint rules.

Security

If you discover a security-related issue, please email mohd.arbaz2207@gmail.com rather than opening a public issue, and allow time for a fix before disclosing it.

Credits

License

The MIT License (MIT). Please see License File for more information.