philiprehberger / laravel-export
Registry-based data export system for Laravel with pluggable format support. Ships with CSV and JSON exporters
Fund package maintenance!
Requires
- php: ^8.2
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- larastan/larastan: ^2.0|^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^9.0|^10.0
- phpstan/extension-installer: ^1.4
- phpunit/phpunit: ^11.0
README
Registry-based data export system for Laravel with pluggable format support. Ships with CSV, JSON, and XML exporters, plus column transformers and queued exports.
Requirements
- PHP 8.2+
- Laravel 11 or 12
Installation
composer require philiprehberger/laravel-export
The service provider is auto-discovered via Laravel's package discovery.
Optionally publish the config file:
php artisan vendor:publish --tag=laravel-export-config
Usage
Export a Collection to CSV
use PhilipRehberger\Export\Facades\Export; use Illuminate\Support\Collection; $data = collect([ ['name' => 'Alice', 'email' => 'alice@example.com', 'plan' => 'pro'], ['name' => 'Bob', 'email' => 'bob@example.com', 'plan' => 'free'], ]); $columns = [ 'name' => 'Name', 'email' => 'Email Address', 'plan' => 'Plan', ]; $csvString = Export::export($data, $columns, 'csv');
Export a Collection to JSON
$jsonString = Export::export($data, $columns, 'json');
Export a Collection to XML
$xmlString = Export::export($data, $columns, 'xml'); // With custom element names $xmlString = Export::export($data, $columns, 'xml', [ 'root_element' => 'users', 'item_element' => 'user', ]);
Download Response (CSV)
Return a download response directly from a controller:
public function download(Request $request): Response { $users = User::all(); $data = $users->map(fn ($u) => [ 'name' => $u->name, 'email' => $u->email, 'created_at' => $u->created_at, ]); $columns = [ 'name' => 'Name', 'email' => 'Email', 'created_at' => 'Registered', ]; return Export::download($data, $columns, 'csv', 'users'); // Sends users.csv with Content-Disposition: attachment }
Stream Response (for large datasets)
return Export::stream($data, $columns, 'csv', 'users'); // Returns a StreamedResponse
Note: Filenames passed to
download()andstream()are automatically sanitized — control characters, quotes, backslashes, and forward slashes are stripped. Empty filenames fall back to"export".
Column Transformers
Use columns() to select, rename, or compute derived columns before exporting. Keys are output column names; values are either a source column name (string) or a callable that receives the full row:
$data = collect([ ['first_name' => 'Alice', 'last_name' => 'Smith', 'email' => 'alice@example.com'], ['first_name' => 'Bob', 'last_name' => 'Jones', 'email' => 'bob@example.com'], ]); $columns = [ 'Full Name' => 'Full Name', 'Email' => 'Email', ]; $csv = Export::columns([ 'Full Name' => fn (array $row) => $row['first_name'] . ' ' . $row['last_name'], 'Email' => 'email', ])->export($data, $columns, 'csv');
The transformer is applied once and automatically resets after the export call.
Queued Exports
For large datasets, dispatch the export to a background queue. The result is stored to a Laravel filesystem disk:
use PhilipRehberger\Export\PendingExport; $pending = Export::queue($data, $columns, 'csv'); // Returns a PendingExport value object $pending->id; // Unique export identifier $pending->disk; // Storage disk (default: 'local') $pending->path; // File path on disk (auto-generated or custom) $pending->status(); // 'pending' or 'completed'
Customize the disk, path, and options:
$pending = Export::queue($data, $columns, 'json', 's3', 'reports/monthly.json', [ 'pretty_print' => false, ]);
Provide an optional callback that runs after the export completes:
$pending = Export::queue($data, $columns, 'csv', 'local', null, [], function (string $path, string $disk) { // Notify user, send email, etc. });
ExportableInterface on Models
Implement ExportableInterface on your Eloquent model to let the service derive columns and filename automatically:
use PhilipRehberger\Export\Contracts\ExportableInterface; class User extends Model implements ExportableInterface { public function toExportArray(): array { return [ 'name' => $this->name, 'email' => $this->email, 'plan' => $this->plan, // enums are auto-unwrapped 'created_at' => $this->created_at, // Carbon dates are auto-formatted ]; } public static function getExportColumns(): array { return [ 'name' => 'Name', 'email' => 'Email Address', 'plan' => 'Plan', 'created_at' => 'Registered', ]; } public static function getExportFilename(): string { return 'users-export'; } }
Then export a collection of models:
$users = User::where('active', true)->get(); // Returns the CSV string $csv = Export::exportModels($users, 'csv'); // Returns a download Response, filename derived from model return Export::downloadModels($users, 'csv'); // Override filename return Export::downloadModels($users, 'csv', 'active-users');
Automatic Value Transformations
AbstractExportFormat handles these transformations automatically inside toExportArray or when using raw collections:
| Type | Transformation |
|---|---|
BackedEnum |
->value |
Carbon\Carbon |
->toDateTimeString() (Y-m-d H:i:s) |
array / object |
json_encode() |
Creating a Custom Format Exporter
Implement ExportFormatInterface (or extend the provided AbstractExportFormat base class):
use Illuminate\Support\Collection; use PhilipRehberger\Export\Formats\AbstractExportFormat; class XmlExporter extends AbstractExportFormat { public function export(Collection $data, array $columns, array $options = []): string { $rows = $this->transformData($data, $columns); $xml = new SimpleXMLElement('<export/>'); foreach ($rows as $row) { $item = $xml->addChild('item'); foreach ($row as $key => $value) { $item->addChild(preg_replace('/\s+/', '_', $key), htmlspecialchars((string) $value)); } } return $xml->asXML(); } public function getContentType(): string { return 'application/xml'; } public function getFileExtension(): string { return 'xml'; } public function getFormatName(): string { return 'xml'; } }
Register it in a service provider:
use PhilipRehberger\Export\ExportFormatRegistry; public function boot(ExportFormatRegistry $registry): void { $registry->register(new XmlExporter); }
Then use it exactly like the built-in formats:
Export::download($data, $columns, 'xml', 'report');
Checking Available Formats
Export::supportsFormat('csv'); // true Export::supportsFormat('excel'); // false (not registered) Export::getAvailableFormats(); // ['csv', 'json', 'xml'] Export::getFormatMetadata(); // [['name'=>'csv', 'extension'=>'csv', 'contentType'=>'text/csv; charset=UTF-8'], ...]
Configuration Reference
Published at config/laravel-export.php:
return [ 'csv' => [ 'delimiter' => ',', 'enclosure' => '"', 'include_bom' => true, // UTF-8 BOM for Excel compatibility 'include_headers' => true, ], 'json' => [ 'pretty_print' => true, 'include_metadata' => false, // wraps output in {metadata:{}, data:[]} ], 'xml' => [ 'root_element' => 'items', 'item_element' => 'item', ], ];
Option Reference
| Option | Format | Default | Description |
|---|---|---|---|
delimiter |
CSV | , |
Field separator |
enclosure |
CSV | " |
String enclosure character |
include_bom |
CSV | true |
Prepend UTF-8 BOM (improves Excel compatibility) |
include_headers |
CSV | true |
Write column headers as the first row |
pretty_print |
JSON | true |
Human-readable indented output |
include_metadata |
JSON | false |
Wrap array in {metadata, data} envelope |
root_element |
XML | items |
Root element name wrapping all items |
item_element |
XML | item |
Element name for each row |
Options can be passed per-call to override the config defaults:
Export::export($data, $columns, 'csv', [ 'delimiter' => ';', 'include_bom' => false, ]);
API
Export Facade / Service
| Method | Description |
|---|---|
Export::export(Collection $data, array $columns, string $format, array $options = []): string |
Export data to a string in the given format |
Export::download(Collection $data, array $columns, string $format, string $filename = 'export', array $options = []): Response |
Return a download response |
Export::stream(Collection $data, array $columns, string $format, string $filename = 'export', array $options = []): StreamedResponse |
Return a streamed download response |
Export::exportModels(Collection $models, string $format, array $options = []): string |
Export a collection of ExportableInterface models |
Export::downloadModels(Collection $models, string $format, ?string $filename = null, array $options = []): Response |
Download a collection of ExportableInterface models |
Export::columns(array $columns): ExportService |
Set a column transformer for the next export call |
Export::queue(Collection $data, array $columns, string $format, string $disk = 'local', ?string $path = null, array $options = [], ?callable $onComplete = null): PendingExport |
Queue an export for background processing |
Export::supportsFormat(string $format): bool |
Check whether a format is registered |
Export::getAvailableFormats(): array |
List all registered format names |
Export::getFormatMetadata(): array |
List format name, extension, and content type for each format |
ExportableInterface
| Method | Description |
|---|---|
toExportArray(): array |
Return the model as an associative array for export |
getExportColumns(): array |
Return column key → label mapping |
getExportFilename(): string |
Return the default export filename (without extension) |
Development
composer install vendor/bin/phpunit vendor/bin/pint --test vendor/bin/phpstan analyse
Support
If you find this project useful: