mohd-arbaaz / laravel-excel-wrapper
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
pkg:composer/mohd-arbaaz/laravel-excel-wrapper
Requires
- php: ^8.4
- illuminate/bus: ^12.0|^13.0
- illuminate/contracts: ^12.0|^13.0
- illuminate/filesystem: ^12.0|^13.0
- illuminate/queue: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- illuminate/validation: ^12.0|^13.0
- illuminate/view: ^12.0|^13.0
- maatwebsite/excel: ^4.0.2
- openspout/openspout: ^5.1
- spatie/laravel-data: ^4.23
Requires (Dev)
- orchestra/testbench: ^11.0
- phpunit/phpunit: ^13.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
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
ColumnDefdata 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
| Feature | Laravel Excel | OpenSpout |
|---|---|---|
| Styling | โ Full support | โ Limited |
| Conditional Formatting | โ Yes | โ No |
| Formulas | โ Yes | โ Basic |
| Memory Usage | Higher | โ 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
- Getting Started
- Column Definitions
- Formatting
- Blade Exports
- Laravel Excel Guide
- OpenSpout Guide
- Multi-Sheet Operations
- Validation
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.