datasuite / laravel-exporter
A fluent, memory-efficient data export package for Laravel supporting CSV, Excel, and JSON formats
Requires
- php: ^8.1
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- symfony/http-foundation: ^6.0|^7.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- phpstan/phpstan: ^1.0
Suggests
- openspout/openspout: For native XLSX file support (^4.0)
README
A fluent, memory-efficient data export package for Laravel supporting CSV, Excel, and JSON formats with rich formatting capabilities.
Features
- 🚀 Fluent API - Clean, chainable methods for building exports
- 💾 Memory Efficient - Uses generators and chunking for large datasets
- 📊 Multiple Formats - CSV, Excel (XLSX/XML), and JSON support
- 🔄 Flexible Data Sources - Works with Eloquent queries, Collections, and arrays
- 🎯 Column Types - Amount, Date, Percentage, Quantity with proper formatting
- 🌍 Multi-Locale Support - Configurable number formats for any country (US, UK, EU, India, Japan, China, etc.)
- 🎨 Conditional Coloring - Configurable colors for positive/negative amounts (optional)
- 📋 Report Headers - Company name, title, date range, generated by (optional)
- ➕ Totals & Subtotals - Automatic calculation of column totals (optional)
- 🔧 Customizable - Transform rows, set headers, and configure format options
- 📦 Zero Dependencies - Excel export works without external libraries (optional OpenSpout support)
Installation
composer require datasuite/laravel-exporter
The package will automatically register its service provider via Laravel's package auto-discovery.
Publish Configuration (Optional)
php artisan vendor:publish --tag=exporter-config
Basic Usage
Using the Facade
use LaravelExporter\Facades\Exporter; // Export users to CSV Exporter::make() ->from(User::query()) ->toFile(storage_path('app/exports/users.csv')); // Export with specific columns Exporter::make() ->columns(['id', 'name', 'email']) ->from(User::query()) ->download('users.csv');
Using the Class Directly
use LaravelExporter\Exporter; $exporter = Exporter::make() ->format('xlsx') ->columns(['id', 'name', 'email']) ->headers(['ID', 'Full Name', 'Email Address']) ->from(User::query()) ->toFile(storage_path('app/exports/users.xlsx'));
Data Sources
The exporter supports multiple data sources:
// Eloquent Query Builder Exporter::make()->from(User::query()); Exporter::make()->from(User::where('active', true)); // Collections Exporter::make()->from(collect($data)); // Arrays Exporter::make()->from($arrayOfData); // LazyCollections (memory efficient) Exporter::make()->from(User::lazy());
Export Formats
CSV Export
Exporter::make() ->format('csv') ->options([ 'delimiter' => ',', 'enclosure' => '"', 'include_headers' => true, 'add_bom' => true, // Excel compatibility ]) ->from($data) ->toFile('export.csv');
Excel Export
Exporter::make() ->format('xlsx') ->options([ 'sheet_name' => 'Users', 'include_headers' => true, ]) ->from($data) ->toFile('export.xlsx');
Note: For native XLSX support, install OpenSpout:
composer require openspout/openspoutWithout OpenSpout, exports will use Excel-compatible XML format.
Excel Export with Column Types
Define column types for proper formatting and optional conditional coloring:
use LaravelExporter\Facades\Exporter; Exporter::make() ->format('xlsx') ->columns(fn($cols) => $cols ->string('order_number', 'Order #') ->date('order_date', 'Date') ->string('customer_name', 'Customer') ->amount('total_amount', 'Amount') // Green for +ve, Red for -ve ->quantity('items_count', 'Items') ->percentage('discount', 'Discount %') ) ->from(Order::query()) ->download('orders.xlsx');
Column Types Available
| Type | Method | Description | Excel Format |
|---|---|---|---|
| String | ->string() |
Plain text | General |
| Integer | ->integer() |
Whole numbers | #,##0 |
| Amount | ->amount() |
Currency with optional conditional coloring | #,##0.00 (locale-based) |
| Amount Plain | ->amountPlain() |
Currency without coloring | #,##0.00 |
| Percentage | ->percentage() |
Percentage values | 0.00% |
| Date | ->date() |
Date values | DD-MMM-YYYY |
| DateTime | ->datetime() |
Date and time | DD-MMM-YYYY HH:MM:SS |
| Boolean | ->boolean() |
Yes/No values | General |
| Quantity | ->quantity() |
Numeric quantities | #,##0.00 |
Report Headers (Optional)
Add professional headers to your exports when needed:
use LaravelExporter\Support\ReportHeader; Exporter::make() ->format('xlsx') ->header(fn($h) => $h ->company('Acme Corporation') ->title('Sales Report') ->subtitle('Monthly Summary') ->dateRange('01-Nov-2024', '30-Nov-2024') ->generatedBy('John Doe') ->generatedAt() ) ->columns(fn($cols) => $cols ->string('invoice_no', 'Invoice #') ->amount('amount', 'Amount') ) ->from($data) ->download('sales-report.xlsx');
Totals Row (Optional)
Automatically calculate and add totals when needed:
Exporter::make() ->format('xlsx') ->columns(fn($cols) => $cols ->string('product', 'Product') ->quantity('qty', 'Quantity') ->amount('price', 'Price') ->amount('total', 'Total') ) ->withTotals(['qty', 'price', 'total']) // Columns to sum ->totalsLabel('GRAND TOTAL') ->from($data) ->download('products.xlsx');
Multi-Locale Support & Conditional Coloring
Configure locale-specific number formatting for any country:
// US format (default): 1,234,567.00 Exporter::make() ->format('xlsx') ->locale('en_US') ->from($data) ->download('report-us.xlsx'); // European format: 1.234.567,00 Exporter::make() ->format('xlsx') ->locale('de_DE') ->from($data) ->download('report-de.xlsx'); // Indian format: 12,34,567.00 Exporter::make() ->format('xlsx') ->locale('en_IN') ->from($data) ->download('report-in.xlsx'); // With optional conditional coloring (green/red for +ve/-ve) Exporter::make() ->format('xlsx') ->locale('en_US') ->conditionalColoring(true) // Optional - enable colored amounts ->columns(fn($cols) => $cols ->string('account', 'Account') ->amount('debit', 'Debit') ->amount('credit', 'Credit') ->amount('balance', 'Balance') ) ->from($ledgerEntries) ->download('ledger.xlsx');
Supported Locales (Configurable)
| Locale | Country | Number Format | Currency |
|---|---|---|---|
en_US |
United States | 1,234,567.00 | $ |
en_GB |
United Kingdom | 1,234,567.00 | £ |
en_IN |
India | 12,34,567.00 | ₹ |
de_DE |
Germany | 1.234.567,00 | € |
fr_FR |
France | 1 234 567,00 | € |
ja_JP |
Japan | 1,234,567 | ¥ |
zh_CN |
China | 1,234,567.00 | ¥ |
Add more locales in config/exporter.php.
JSON Export
Exporter::make() ->format('json') ->options([ 'pretty_print' => true, 'wrap_in_object' => true, 'data_key' => 'users', 'include_metadata' => true, ]) ->from($data) ->toFile('export.json');
Column Selection
Simple Columns
Exporter::make() ->columns(['id', 'name', 'email']) ->from(User::query());
Column Aliases
Exporter::make() ->columns([ 'User ID' => 'id', 'Full Name' => 'name', 'Email Address' => 'email', ]) ->from(User::query());
Nested Columns (Dot Notation)
Exporter::make() ->columns([ 'id', 'name', 'department.name', // Access related model ]) ->from(User::with('department'));
Custom Headers
Exporter::make() ->columns(['id', 'name', 'email']) ->headers(['User ID', 'Full Name', 'Email Address']) ->from(User::query());
Row Transformation
Transform each row before export:
Exporter::make() ->transformRow(function (array $row, $originalItem) { $row['name'] = strtoupper($row['name']); $row['status'] = $originalItem->isActive() ? 'Active' : 'Inactive'; return $row; }) ->from(User::query());
Output Methods
Save to File
Exporter::make() ->from($data) ->toFile(storage_path('app/exports/data.csv'));
Download Response
return Exporter::make() ->from($data) ->download('data.csv');
Stream Response (Memory Efficient)
return Exporter::make() ->from($data) ->stream('data.csv');
Get as String
$content = Exporter::make() ->from($data) ->toString();
Using the Exportable Trait
Add export functionality directly to your models:
use LaravelExporter\Traits\Exportable; class User extends Model { use Exportable; // Optional: Define default exportable columns protected array $exportable = ['id', 'name', 'email']; // Optional: Define default headers protected array $exportHeaders = ['ID', 'Full Name', 'Email']; }
Then use it like this:
// Export with model defaults User::query()->export()->toFile('users.csv'); // Export with custom columns User::where('active', true) ->export(['id', 'name']) ->download('active-users.csv'); // Quick export all User::exportAll('csv', storage_path('users.csv'));
Controller Example
use LaravelExporter\Facades\Exporter; class ExportController extends Controller { public function exportUsers(Request $request) { $format = $request->get('format', 'csv'); return Exporter::make() ->format($format) ->columns(['id', 'name', 'email', 'created_at']) ->headers(['ID', 'Name', 'Email', 'Registered At']) ->from(User::query()) ->download("users.{$format}"); } }
Memory Optimization
For large datasets, the package automatically uses:
- Generators - Data is processed one row at a time
- Lazy Collections - Eloquent queries use
lazy()for memory efficiency - Chunking - Configure chunk size for optimal performance
Exporter::make() ->chunkSize(500) // Process 500 rows at a time ->from(User::query()) ->toFile('large-export.csv');
Configuration
Publish the config file to customize defaults:
// config/exporter.php return [ 'default_format' => 'csv', 'chunk_size' => 1000, 'csv' => [ 'delimiter' => ',', 'enclosure' => '"', 'include_headers' => true, 'add_bom' => true, ], 'excel' => [ 'include_headers' => true, 'sheet_name' => 'Sheet1', ], 'json' => [ 'pretty_print' => false, 'wrap_in_object' => false, ], ];
Requirements
- PHP 8.1+
- Laravel 10.x or 11.x
Optional Dependencies
openspout/openspout- For native XLSX file supportphpoffice/phpspreadsheet- For advanced Excel features (formulas, conditional formatting, cell merging)
Maatwebsite Excel-Style Exports (NEW!)
If you're familiar with Maatwebsite Excel, you'll feel right at home! We now support the same interface-based Concerns pattern for cleaner, reusable export classes.
Quick Start
// Download export use App\Exports\UsersExport; use LaravelExporter\Facades\Excel; return Excel::download(new UsersExport, 'users.xlsx');
Creating an Export Class
Simple Export (FromCollection)
<?php namespace App\Exports; use App\Models\User; use LaravelExporter\Concerns\FromCollection; use LaravelExporter\Concerns\WithHeadings; use LaravelExporter\Concerns\Exportable; class UsersExport implements FromCollection, WithHeadings { use Exportable; public function collection() { return User::all(); } public function headings(): array { return ['ID', 'Name', 'Email', 'Created At']; } }
Query-Based Export (FromQuery) - Memory Efficient
<?php namespace App\Exports; use App\Models\Product; use Illuminate\Database\Eloquent\Builder; use LaravelExporter\Concerns\FromQuery; use LaravelExporter\Concerns\WithHeadings; use LaravelExporter\Concerns\WithMapping; use LaravelExporter\Concerns\ShouldAutoSize; use LaravelExporter\Concerns\Exportable; class ProductsExport implements FromQuery, WithHeadings, WithMapping, ShouldAutoSize { use Exportable; public function query(): Builder { return Product::query()->orderBy('category'); } public function headings(): array { return ['SKU', 'Name', 'Category', 'Price', 'Stock']; } public function map($product): array { return [ $product->sku, $product->name, $product->category, '₹' . number_format($product->price, 2), $product->stock_quantity, ]; } }
Full-Featured Export with Styles
<?php namespace App\Exports; use App\Models\Order; use Illuminate\Database\Eloquent\Builder; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use LaravelExporter\Concerns\FromQuery; use LaravelExporter\Concerns\WithHeadings; use LaravelExporter\Concerns\WithMapping; use LaravelExporter\Concerns\WithStyles; use LaravelExporter\Concerns\WithColumnFormatting; use LaravelExporter\Concerns\WithTotals; use LaravelExporter\Concerns\WithReportHeader; use LaravelExporter\Concerns\Exportable; use LaravelExporter\Support\ReportHeader; class OrdersExport implements FromQuery, WithHeadings, WithMapping, WithStyles, WithColumnFormatting, WithTotals, WithReportHeader { use Exportable; public function query(): Builder { return Order::query()->with('user')->limit(10000); } public function headings(): array { return ['Order #', 'Customer', 'Status', 'Total', 'Date']; } public function map($order): array { return [ $order->order_number, $order->user->name, ucfirst($order->status), $order->total, $order->created_at->format('Y-m-d'), ]; } public function styles(Worksheet $sheet): array { return [ 1 => ['font' => ['bold' => true]], 'A' => ['font' => ['bold' => true]], ]; } public function columnFormats(): array { return [ 'D' => NumberFormat::FORMAT_NUMBER_COMMA_SEPARATED1, ]; } public function totalColumns(): array { return ['Total']; } public function totalLabel(): string { return 'GRAND TOTAL'; } public function reportHeader(): ReportHeader { return ReportHeader::make() ->title('Orders Report') ->addLine('Generated: ' . now()->format('d-M-Y')); } }
Available Concerns
| Concern | Description |
|---|---|
FromCollection |
Use a Laravel Collection as data source |
FromQuery |
Use an Eloquent Builder (memory efficient with cursor) |
FromArray |
Use a plain PHP array as data source |
FromGenerator |
Use a Generator for custom iteration |
WithHeadings |
Add column headers to export |
WithMapping |
Transform each row before exporting |
WithChunkReading |
Process data in chunks for very large datasets |
WithColumnFormatting |
Apply number/date formats (requires PhpSpreadsheet) |
WithColumnWidths |
Set fixed column widths |
WithStyles |
Apply cell styles (requires PhpSpreadsheet) |
ShouldAutoSize |
Auto-size columns to fit content |
WithTitle |
Set worksheet title |
WithMultipleSheets |
Export multiple sheets in one file |
WithTotals |
Add a totals/summary row |
WithReportHeader |
Add report header block |
WithColumnDefinitions |
Use column type definitions |
WithConditionalColoring |
Enable conditional coloring |
WithFreezeRow |
Freeze header row |
WithAutoFilter |
Add auto-filter dropdown |
WithEvents |
Register event hooks |
Chunked Reading for Large Datasets
For very large datasets (100K+ rows), use WithChunkReading to process data in batches:
use LaravelExporter\Concerns\FromQuery; use LaravelExporter\Concerns\WithChunkReading; use LaravelExporter\Concerns\Exportable; class LargeExport implements FromQuery, WithChunkReading { use Exportable; public function query(): Builder { return Order::query(); } public function chunkSize(): int { return 1000; // Process 1000 rows at a time } }
Memory Comparison:
| Method | Memory Usage | Best For |
|---|---|---|
FromCollection |
High (loads all) | Small datasets (<1K rows) |
FromQuery (cursor) |
Medium | Medium datasets (1K-50K rows) |
FromQuery + WithChunkReading |
Low | Large datasets (50K+ rows) |
Using the Exportable Trait
The Exportable trait adds convenient methods to your export class:
use App\Exports\UsersExport; // From within a controller return (new UsersExport)->download('users.xlsx'); // Or store to disk (new UsersExport)->store('exports/users.xlsx', 'local');
Multiple Sheets
<?php namespace App\Exports; use LaravelExporter\Concerns\WithMultipleSheets; use LaravelExporter\Concerns\Exportable; class SalesReportExport implements WithMultipleSheets { use Exportable; public function sheets(): array { return [ 'Products' => new ProductsExport(), 'Orders' => new OrdersExport(), ]; } }
Comparison: Fluent API vs Concerns
Both styles are supported! Choose what works best for your use case:
| Fluent API | Concerns Pattern |
|---|---|
| Quick, inline exports | Reusable export classes |
| Good for simple exports | Better for complex exports |
| Less boilerplate | More organized |
Exporter::make()->from($data) |
Excel::download(new Export) |
Importing Data (Maatwebsite-Style)
The package also supports importing data from CSV, Excel, and JSON files using the same Concerns pattern as Maatwebsite Excel.
Basic Import
use LaravelExporter\Facades\Excel; use App\Imports\UsersImport; // Import from file Excel::import(new UsersImport, 'users.xlsx'); // Import from uploaded file Excel::import(new UsersImport, $request->file('file')); // Import from storage disk Excel::import(new UsersImport, 'imports/users.xlsx', 's3');
Creating an Import Class
<?php namespace App\Imports; use App\Models\User; use LaravelExporter\Concerns\ToModel; use LaravelExporter\Concerns\WithHeadingRow; use LaravelExporter\Concerns\WithValidation; use LaravelExporter\Concerns\Importable; class UsersImport implements ToModel, WithHeadingRow, WithValidation { use Importable; public function model(array $row): User { return new User([ 'name' => $row['name'], 'email' => $row['email'], 'password' => bcrypt($row['password']), ]); } public function headingRow(): int { return 1; } public function rules(): array { return [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', ]; } public function customValidationMessages(): array { return []; } public function customValidationAttributes(): array { return []; } }
Import Concerns
| Concern | Description |
|---|---|
ToModel |
Convert each row to an Eloquent model |
ToCollection |
Process all rows as a Collection |
ToArray |
Process all rows as an array |
OnEachRow |
Process each row individually |
WithHeadingRow |
Use first row as array keys |
WithValidation |
Validate each row |
WithBatchInserts |
Insert models in batches |
WithUpserts |
Update existing or create new |
WithChunkReading |
Read file in chunks |
SkipsOnError |
Skip rows that cause errors |
SkipsOnFailure |
Skip rows that fail validation |
WithStartRow |
Start reading from specific row |
WithLimit |
Limit number of rows |
WithColumnLimit |
Limit columns to read |
WithCalculatedFormulas |
Get formula results |
WithMappedCells |
Read specific cells |
WithMultipleSheets |
Handle multiple sheets |
Import with Upserts (Update or Create)
use LaravelExporter\Concerns\ToModel; use LaravelExporter\Concerns\WithUpserts; use LaravelExporter\Concerns\WithBatchInserts; class ProductsImport implements ToModel, WithUpserts, WithBatchInserts { public function model(array $row): Product { return new Product([ 'sku' => $row['sku'], 'name' => $row['name'], 'price' => $row['price'], ]); } public function uniqueBy(): string { return 'sku'; // Update if SKU exists } public function batchSize(): int { return 500; // Insert 500 at a time } }
Handling Validation Failures
use LaravelExporter\Concerns\WithValidation; use LaravelExporter\Concerns\SkipsOnFailure; use LaravelExporter\Imports\Failure; class UsersImport implements ToModel, WithValidation, SkipsOnFailure { protected array $failures = []; public function rules(): array { return ['email' => 'required|email|unique:users']; } public function onFailure(Failure ...$failures): void { $this->failures = array_merge($this->failures, $failures); } public function getFailures(): array { return $this->failures; } } // Usage $import = new UsersImport; Excel::import($import, 'users.xlsx'); foreach ($import->getFailures() as $failure) { echo "Row {$failure->row()}: " . implode(', ', $failure->errors()); }
ToCollection Import
use Illuminate\Support\Collection; use LaravelExporter\Concerns\ToCollection; use LaravelExporter\Concerns\WithHeadingRow; class SalesDataImport implements ToCollection, WithHeadingRow { protected array $summary = []; public function collection(Collection $collection): void { $this->summary = [ 'total_rows' => $collection->count(), 'total_revenue' => $collection->sum('amount'), 'average_order' => $collection->avg('amount'), ]; } public function headingRow(): int { return 1; } public function getSummary(): array { return $this->summary; } }
Converting to Array/Collection
// Get raw data as array $rows = Excel::toArray(new UsersImport, 'users.xlsx'); // Get as Collection $collection = Excel::toCollection(new UsersImport, 'users.xlsx');
Multi-Sheet Import
use LaravelExporter\Concerns\WithMultipleSheets; class WorkbookImport implements WithMultipleSheets { public function sheets(): array { return [ 0 => new UsersImport(), // First sheet 1 => new ProductsImport(), // Second sheet // Or by name: // 'Users' => new UsersImport(), ]; } }
Using the Importable Trait
use LaravelExporter\Concerns\Importable; class UsersImport implements ToModel { use Importable; // ... } // Usage $import = new UsersImport; $import->import('users.xlsx'); // Or $array = $import->toArray('users.xlsx'); $collection = $import->toCollection('users.xlsx');
Import Result
$result = Excel::import(new UsersImport, 'users.xlsx'); echo "Total rows: " . $result->totalRows(); echo "Imported: " . $result->importedRows(); echo "Skipped: " . $result->skippedRows(); echo "Failed: " . $result->failedRows(); echo "Success rate: " . $result->successRate() . "%"; echo "Duration: " . $result->duration() . "s"; echo "Memory: " . $result->peakMemoryFormatted(); if ($result->errors()->hasFailures()) { foreach ($result->errors()->failures() as $failure) { // Handle failures } }
Supported File Formats
| Format | Extension | Reader |
|---|---|---|
| CSV | .csv, .txt, .tsv | Native (streaming) |
| Excel | .xlsx, .xls | OpenSpout (streaming) |
| JSON | .json | Native |
License
MIT License