A powerful Laravel package for exporting large datasets in batches and chunks with support for CSV and XLSX formats. This package provides a clean, fluent API for handling exports efficiently.
- ✅ Batch & Chunk Processing - Handle large datasets efficiently with configurable chunk sizes
- ✅ Queue-Based Exports - Process exports asynchronously using Laravel queues
- ✅ Multiple Formats - Export to both CSV and XLSX formats
- ✅ Column Mapping - Customize column names and labels
- ✅ Progress Tracking - Monitor export progress with detailed status information
- ✅ Query Builder Support - Works with both Eloquent Builder and Query Builder
- ✅ Fluent API - Laravel-style fluent interface for easy configuration
- ✅ Relationship Support - Export related model data with aggregations
- ✅ Custom Formatting - Format dates, numbers, and custom values
- ✅ Storage Flexibility - Support for local, S3, and custom storage disks
- ✅ Event System - Listen to export completion events
- PHP 8.1 or higher
- Laravel 10.0, 11.0, or 12.0
- Queue driver configured (database, redis, sqs, etc.)
Install the package via Composer:
composer require osama-98/laravel-exportsPublish and run the migrations:
# Publish the package migrations
php artisan vendor:publish --tag=laravel-exports-migrations
# Publish Laravel's queue migrations (required for job batching)
php artisan queue:batches-table
php artisan migrateNote: The job_batches table is required for queue batching functionality. If you're using the database queue driver, you'll also need the jobs and failed_jobs tables:
php artisan queue:table
php artisan queue:failed-table
php artisan migrateThe package will automatically register its service provider.
Create an exporter class that extends Osama\LaravelExports\Exports\Exporter:
<?php
namespace App\Exports;
use App\Models\User;
use Carbon\Carbon;
use Osama\LaravelExports\Exports\ExportColumn;
use Osama\LaravelExports\Exports\Exporter;
use Osama\LaravelExports\Exports\Models\Export;
class UserExporter extends Exporter
{
public static function getModel(): string
{
return User::class;
}
public static function getColumns(): array
{
return [
ExportColumn::make('id')
->label('ID'),
ExportColumn::make('name')
->label('Full Name'),
ExportColumn::make('email')
->label('Email Address'),
ExportColumn::make('created_at')
->label('Created At')
->formatStateUsing(fn ($state) => $state ? Carbon::parse($state)->format('Y-m-d H:i:s') : null),
];
}
public static function getCompletedNotificationBody(Export $export): string
{
return "Your user export has completed with {$export->successful_rows} rows exported.";
}
}use App\Exports\UserExporter;
use App\Models\User;
use Osama\LaravelExports\Facades\Export as ExportFacade;
use Osama\LaravelExports\Exports\Enums\ExportFormat;
// Basic export to XLSX
$export = ExportFacade::exporter(UserExporter::class)
->formats([ExportFormat::Xlsx])
->start(User::query(), creator: auth()->user());
// Export to both CSV and XLSX
$export = ExportFacade::exporter(UserExporter::class)
->formats([ExportFormat::Csv, ExportFormat::Xlsx])
->chunkSize(500)
->start(User::query(), creator: auth()->user());use App\Exports\UserExporter;
use App\Models\User;
use Osama\LaravelExports\Exports\ExportManager;
use Osama\LaravelExports\Exports\Enums\ExportFormat;
public function exportUsers(ExportManager $exportManager)
{
$export = $exportManager
->exporter(UserExporter::class)
->chunkSize(500)
->formats([ExportFormat::Csv, ExportFormat::Xlsx])
->start(User::query(), creator: auth()->user());
return response()->json([
'export_id' => $export->id,
'status' => $export->status->value,
'total_rows' => $export->total_rows,
]);
}use Osama\LaravelExports\Exports\Models\Export;
use Osama\LaravelExports\Exports\Enums\ExportStatus;
$export = Export::find($exportId);
// Check status
if ($export->status === ExportStatus::Completed) {
// Export is ready
}
// Get progress percentage
$progress = $export->progressPercentage();use Osama\LaravelExports\Exports\Models\Export;
use Osama\LaravelExports\Exports\Enums\ExportStatus;
public function downloadExport(Export $export, string $format = 'xlsx')
{
if ($export->status !== ExportStatus::Completed) {
abort(400, 'Export is not completed yet');
}
return $export->download($format);
}$export = ExportFacade::exporter(UserExporter::class)
->formats([ExportFormat::Xlsx])
->start(
User::query(),
columnMap: [
'id' => 'User ID',
'name' => 'Full Name',
'email' => 'Email Address',
'created_at' => 'Registration Date',
],
creator: auth()->user()
);$export = ExportFacade::exporter(UserExporter::class)
->modifyQueryUsing(function ($query, $options) {
// Filter users created in the last 30 days
return $query->where('created_at', '>=', now()->subDays(30));
})
->formats([ExportFormat::Csv])
->start(User::query(), creator: auth()->user());$userIds = User::take(100)->pluck('id')->toArray();
$export = ExportFacade::exporter(UserExporter::class)
->formats([ExportFormat::Csv])
->start(User::query(), records: $userIds, creator: auth()->user());$export = ExportFacade::exporter(UserExporter::class)
->maxRows(1000)
->chunkSize(200)
->formats([ExportFormat::Csv])
->start(User::query(), creator: auth()->user());$export = ExportFacade::exporter(UserExporter::class)
->options([
'date_format' => 'Y-m-d',
'timezone' => 'UTC',
])
->fileDisk('local')
->fileName('custom-users-export')
->formats([ExportFormat::Xlsx])
->start(User::query(), creator: auth()->user());use Illuminate\Support\Facades\DB;
$export = ExportFacade::exporter(UserExporter::class)
->formats([ExportFormat::Csv])
->start(DB::table('users')->where('active', true), creator: auth()->user());$export = ExportFacade::exporter(UserExporter::class)
->fileDisk('s3') // Make sure 's3' disk is configured in config/filesystems.php
->formats([ExportFormat::Xlsx])
->start(User::query()->limit(1000), creator: auth()->user());Customize how column values are extracted:
ExportColumn::make('full_name')
->label('Full Name')
->getStateUsing(function ($user) {
return "{$user->first_name} {$user->last_name}";
});
// Use dot notation for relationships
ExportColumn::make('profile.bio')
->label('Biography');
// Use default value
ExportColumn::make('status')
->default('active');Format dates, numbers, and add prefixes/suffixes:
ExportColumn::make('price')
->label('Price')
->prefix('$')
->formatStateUsing(fn ($state) => number_format($state, 2));
ExportColumn::make('created_at')
->label('Created At')
->formatStateUsing(fn ($state) => $state ? Carbon::parse($state)->format('Y-m-d H:i:s') : null);
ExportColumn::make('description')
->label('Description')
->limit(100); // Limit text lengthExport aggregated data from relationships:
ExportColumn::make('orders_count')
->label('Total Orders')
->counts('orders');
ExportColumn::make('orders_avg_amount')
->label('Average Order Amount')
->avg('orders', 'amount');
ExportColumn::make('orders_sum_amount')
->label('Total Order Amount')
->sum('orders', 'amount');
ExportColumn::make('has_orders')
->label('Has Orders')
->exists('orders');Modify queries directly in your exporter class:
class UserExporter extends Exporter
{
public static function modifyQuery($query)
{
return $query->where('active', true)
->with('profile');
}
}Customize XLSX cell styles:
use OpenSpout\Common\Entity\Style\Style;
use OpenSpout\Common\Entity\Style\Color;
use OpenSpout\Common\Entity\Style\CellAlignment;
class UserExporter extends Exporter
{
public function getXlsxCellStyle(): ?Style
{
return (new Style())
->setFontSize(12)
->setFontColor(Color::BLACK);
}
public function getXlsxHeaderCellStyle(): ?Style
{
return (new Style())
->setFontSize(14)
->setFontBold()
->setFontColor(Color::WHITE)
->setBackgroundColor(Color::BLUE)
->setCellAlignment(CellAlignment::CENTER);
}
}The ExportCompleted event is dispatched when an export finishes processing. Listen to this event to send notifications or perform post-export actions.
Example Listener:
// In your EventServiceProvider
use Osama\LaravelExports\Exports\Events\ExportCompleted;
use Illuminate\Support\Facades\Event;
Event::listen(ExportCompleted::class, function (ExportCompleted $event) {
$export = $event->export;
// Send notification to user
if ($export->creator) {
$export->creator->notify(
new ExportReadyNotification($export)
);
}
// Or log the completion
Log::info("Export {$export->id} completed with {$export->successful_rows} rows");
});Example Listener Class:
namespace App\Listeners;
use Osama\LaravelExports\Exports\Events\ExportCompleted;
use Illuminate\Support\Facades\Notification;
class SendExportCompletionNotification
{
public function handle(ExportCompleted $event): void
{
$export = $event->export;
if ($export->creator) {
$export->creator->notify(
new ExportReadyNotification($export)
);
}
}
}Register it in EventServiceProvider:
protected $listen = [
\Osama\LaravelExports\Exports\Events\ExportCompleted::class => [
\App\Listeners\SendExportCompletionNotification::class,
],
];The package uses Laravel's queue batching system for processing exports. Make sure your queue is properly configured.
If you're using the database queue driver, you need the following tables:
job_batches- Required for batch processing (used by this package)jobs- Required for queue jobsfailed_jobs- Required for failed job tracking
Create these tables:
php artisan queue:batches-table
php artisan queue:table
php artisan queue:failed-table
php artisan migrateConfigure queue in .env:
QUEUE_CONNECTION=database
# or
QUEUE_CONNECTION=redisRun queue worker:
php artisan queue:workFor production, use a process manager like Supervisor to keep the queue worker running.
| Method | Description | Example |
|---|---|---|
exporter(string $exporter) |
Set the exporter class | ->exporter(UserExporter::class) |
start(Builder|QueryBuilder|null $query, ?array $records, ?array $columnMap, $creator) |
Start the export | ->start(User::query(), creator: auth()->user()) |
chunkSize(int $size) |
Set chunk size (default: 100) | ->chunkSize(500) |
maxRows(?int $rows) |
Set maximum rows limit | ->maxRows(10000) |
formats(array $formats) |
Set export formats | ->formats([ExportFormat::Csv, ExportFormat::Xlsx]) |
fileDisk(?string $disk) |
Set storage disk | ->fileDisk('s3') |
fileName(?string $name) |
Set custom file name | ->fileName('my-export') |
modifyQueryUsing(?Closure $callback) |
Modify query before export | ->modifyQueryUsing(fn($q) => $q->where('active', true)) |
options(array $options) |
Set export options | ->options(['date_format' => 'Y-m-d']) |
| Method | Description | Example |
|---|---|---|
make(string $name) |
Create a new column | ExportColumn::make('name') |
label(string $label) |
Set column label | ->label('Full Name') |
getStateUsing(Closure|string $callback) |
Custom state extraction | ->getStateUsing(fn($r) => $r->name) |
formatStateUsing(Closure $callback) |
Format the state value | ->formatStateUsing(fn($s) => ucfirst($s)) |
enabled(bool $enabled) |
Enable/disable by default | ->enabled(false) |
default($value) |
Set default value | ->default('N/A') |
prefix(string $prefix) |
Add prefix to value | ->prefix('$') |
suffix(string $suffix) |
Add suffix to value | ->suffix(' USD') |
limit(int $limit) |
Limit text length | ->limit(100) |
counts(string $relationship) |
Count relationship records | ->counts('orders') |
sum(string $relationship, string $column) |
Sum relationship column | ->sum('orders', 'amount') |
avg(string $relationship, string $column) |
Average relationship column | ->avg('orders', 'amount') |
| Property | Type | Description |
|---|---|---|
id |
int | Export ID |
exporter |
string | Exporter class name |
status |
ExportStatus | Export status (Pending, Processing, Completed, Failed) |
total_rows |
int | Total number of rows to export |
processed_rows |
int | Number of rows processed |
successful_rows |
int | Number of rows successfully exported |
file_name |
string | Export file name |
file_disk |
string | Storage disk name |
completed_at |
Carbon|null | Completion timestamp |
creator |
Model|null | User who created the export (morphTo) |
| Method | Description | Example |
|---|---|---|
download(string $format) |
Download export file | $export->download('xlsx') |
getFileDirectory() |
Get file directory path | $export->getFileDirectory() |
getFileDisk() |
Get Filesystem instance | $export->getFileDisk() |
getFailedRowsCount() |
Get count of failed rows | $export->getFailedRowsCount() |
progressPercentage() |
Get progress percentage (0-100) | $export->progressPercentage() |
Run the test suite:
composer testRun tests with coverage:
composer test-coverageContributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This package is open-sourced software licensed under the MIT license.
For issues, questions, or contributions, please visit the GitHub repository.
Developed with ❤️ for the Laravel community.