Advanced export functionality for Filament resources with dynamic column selection, filtering, ordering, and background processing.
filamentphp/advanced-export is a Laravel package for advanced export functionality for filament resources with dynamic column selection, filtering, ordering, and background processing..
It currently has 8 GitHub stars and 1.256 downloads on Packagist (latest version v0.3.0).
Install it with composer require filamentphp/advanced-export.
Discover more Laravel packages by filamentphp
or browse all Laravel packages to compare alternatives.
Last updated
Advanced export functionality for Filament resources with dynamic column selection, filtering, ordering, and background processing.
Install the package via Composer:
composer require filamentphp/advanced-export
Run the installation command:
php artisan export:install
This will:
If you prefer manual setup, publish assets individually:
# Publish configuration
php artisan export:publish --config
# Publish views
php artisan export:publish --views
# Publish translations
php artisan export:publish --lang
# Publish stubs
php artisan export:publish --stubs
# Publish everything
php artisan export:publish --all
The fastest way to add export functionality to any Filament resource:
php artisan export:resource "App\Filament\Resources\ClienteResource"
This single command will:
Exportable interface, trait, and export methodsHasAdvancedExport trait and export actionThat's it! Your resource now has full export functionality.
# Force overwrite existing files
php artisan export:resource "App\Filament\Resources\ClienteResource" --force
If you prefer more control, you can set up each component individually:
php artisan export:model App\\Models\\Cliente
Or manually add the interface and methods:
<?php
namespace App\Models;
use Filament\AdvancedExport\Contracts\Exportable;
use Filament\AdvancedExport\Traits\InteractsWithExportable;
use Illuminate\Database\Eloquent\Model;
class Cliente extends Model implements Exportable
{
use InteractsWithExportable;
public static function getExportColumns(): array
{
return [
'id' => 'ID',
'nome' => 'Name',
'email' => 'Email',
'telefone' => 'Phone',
'status' => 'Status',
'created_at' => 'Created At',
];
}
public static function getDefaultExportColumns(): array
{
return [
['field' => 'id', 'title' => 'ID'],
['field' => 'nome', 'title' => 'Full Name'],
['field' => 'email', 'title' => 'Email Address'],
['field' => 'status', 'title' => 'Status'],
];
}
}
php artisan export:views App\\Models\\Cliente
This creates:
resources/views/exports/clientes-excel.blade.php (simple export)resources/views/exports/clientes-excel-advanced.blade.php (advanced export)<?php
namespace App\Filament\Resources\Cliente\Pages;
use App\Filament\Resources\ClienteResource;
use Filament\AdvancedExport\Traits\HasAdvancedExport;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListClientes extends ListRecords
{
use HasAdvancedExport;
protected static string $resource = ClienteResource::class;
protected function getHeaderActions(): array
{
return [
$this->getAdvancedExportHeaderAction(),
CreateAction::make(),
];
}
}
One of the key features of this package is automatic filter support. When users apply filters to your Filament table (e.g., ?filters[status][values][0]=pending), the export will automatically respect those filters.
The package automatically:
WHERE or WHERE IN clause| Filter Type | Example URL | Query Applied |
|-------------|-------------|---------------|
| Single value | ?filters[status][value]=active | WHERE status = 'active' |
| Multiple values | ?filters[status][values][0]=pending&filters[status][values][1]=active | WHERE status IN ('pending', 'active') |
| Date range | ?filters[created_at][from]=2024-01-01&filters[created_at][until]=2024-12-31 | WHERE created_at BETWEEN ... |
For complex filters that don't map directly to columns, override the applyCustomFilter method:
class ListClientes extends ListRecords
{
use HasAdvancedExport;
protected function applyCustomFilter($query, string $filterName, mixed $filterValue): void
{
match ($filterName) {
'has_orders' => $query->whereHas('orders'),
'premium_customer' => $query->where('total_spent', '>', 10000),
default => $this->applyGenericFilter($query, $filterName, $filterValue),
};
}
}
The configuration file is published to config/advanced-export.php:
return [
// Export limits
'limits' => [
'max_records' => 2000,
'chunk_size' => 500,
'queue_threshold' => 2000,
],
// View configuration
'views' => [
'path' => 'exports',
'simple_suffix' => '-excel',
'advanced_suffix' => '-excel-advanced',
'use_package_views' => false,
],
// Date formatting
'date_format' => 'd/m/Y H:i',
// File generation
'file' => [
'extension' => 'xlsx', // Default format: 'xlsx' or 'csv'
'disk' => 'public',
'directory' => 'exports',
'supported_formats' => ['xlsx', 'csv'], // Formats shown in modal
],
// Action button appearance
'action' => [
'name' => 'export',
'label' => null,
'icon' => 'heroicon-o-arrow-down-tray',
'color' => 'success',
],
// Fallback filters (used when dynamic extraction fails)
'fallback_filters' => [
'created_at',
'updated_at',
],
// Queue settings
'queue' => [
'enabled' => true,
'connection' => 'default',
'queue' => 'exports',
],
];
The export modal includes a format selector. Users can choose between XLSX and CSV:
// config/advanced-export.php
'file' => [
'extension' => 'xlsx', // Default format
'supported_formats' => ['xlsx', 'csv'], // Available in modal
],
CSV exports use the CsvExport class which implements FromCollection and WithHeadings for clean tabular output without view templates.
The export modal shows how many records will be exported before the user clicks the export button. This count respects active filters and the configured maximum limit.
When dynamic filter extraction fails (e.g., non-standard table implementations), the package falls back to a configurable list of filter names:
// config/advanced-export.php
'fallback_filters' => [
'created_at',
'updated_at',
// Add your resource-specific filters here
],
You can also override per-resource:
class ListClientes extends ListRecords
{
use HasAdvancedExport;
protected function getFallbackFilterNames(): array
{
return ['created_at', 'updated_at', 'status', 'cliente_id'];
}
}
Export errors show a generic message to the user and log the full error internally:
asc or desc (case-insensitive), invalid values fall back to descclient.name) are skipped gracefully in ordering -- override applyCustomOrdering() to handle them with joins
You can export relationship data simply by using the relationship name as the column key:
public static function getExportColumns(): array
{
return [
'id' => 'ID',
'declaration_number' => 'Declaration Number',
'insurer' => 'Insurer', // Will automatically load the relationship
'status' => 'Status',
];
}
The package will automatically detect and load the relationship, displaying the related model's default display value.
For more specific relationship data (like a specific attribute), use dot notation:
public static function getExportColumns(): array
{
return [
'id' => 'ID',
'insurer.name' => 'Insurer Name', // Specific attribute
'insurer.nuit' => 'Insurer NUIT', // Another attribute
'status' => 'Status',
];
}
To optimize performance, specify relationships to eager load:
class ListDeclarations extends ListRecords
{
use HasAdvancedExport;
protected function getExportRelationshipsForModel(): array
{
return ['insurer', 'payments', 'createdBy'];
}
}
The package validates order columns and directions automatically. Dot notation columns (relationship paths) are skipped by default.
To handle ordering by relationship columns, override applyCustomOrdering():
class ListClientes extends ListRecords
{
use HasAdvancedExport;
protected function applyCustomOrdering($query, string $orderColumn, string $orderDirection): void
{
// Direction is already validated ('asc' or 'desc') at this point
if ($orderColumn === 'insurer.name') {
$query->join('insurers', 'declarations.insurer_id', '=', 'insurers.id')
->orderBy('insurers.name', $orderDirection);
return;
}
// Call parent for standard column validation and ordering
parent::applyCustomOrdering($query, $orderColumn, $orderDirection);
}
}
If you don't want to create custom views for each model:
// config/advanced-export.php
'views' => [
'use_package_views' => true,
],
export:resource (Recommended)Complete setup for a Filament resource in one command:
# Basic usage
php artisan export:resource "App\Filament\Resources\ClienteResource"
# Force overwrite existing files
php artisan export:resource "App\Filament\Resources\ClienteResource" --force
This command:
$model propertygetPages()export:model to configure the modelexport:views to generate Blade templatesexport:installInitial package setup:
php artisan export:install
php artisan export:install --panel=admin
php artisan export:install --no-interaction
export:modelAdd export methods to a model:
php artisan export:model App\\Models\\Cliente
php artisan export:model App\\Models\\Cliente --columns=id,nome,email,created_at
php artisan export:model App\\Models\\Cliente --force
export:viewsGenerate export views for a model:
php artisan export:views App\\Models\\Cliente
php artisan export:views App\\Models\\Cliente --force
export:publishPublish package assets:
php artisan export:publish --config
php artisan export:publish --views
php artisan export:publish --stubs
php artisan export:publish --lang
php artisan export:publish --migrations
php artisan export:publish --all
php artisan export:publish --all --force
The package includes translations for:
en)pt)To add more languages, publish the translations and create new language files:
php artisan export:publish --lang
Then create resources/lang/vendor/advanced-export/{locale}/messages.php.
The package includes a powerful background export feature with automatic database notifications. This is ideal for large exports that would otherwise timeout.
Add the background export action to your ListRecords page:
use Filament\AdvancedExport\Traits\HasAdvancedExport;
class ListDeclarations extends ListRecords
{
use HasAdvancedExport;
protected function getHeaderActions(): array
{
return [
$this->getAdvancedExportHeaderAction(), // Synchronous export
$this->getBackgroundExportAction(), // Background export with notifications
CreateAction::make(),
];
}
}
ProcessExportJob is dispatched to the queueMake sure your Filament panel has database notifications enabled:
// app/Providers/Filament/AdminPanelProvider.php
public function panel(Panel $panel): Panel
{
return $panel
->default()
->id('admin')
->databaseNotifications() // Enable this!
// ...
}
Background exports require queue workers. Run both the exports queue and the default queue (for notifications):
# In separate terminals or using a process manager like Supervisor:
php artisan queue:work --queue=exports
php artisan queue:work --queue=default
Or use a single worker for both:
php artisan queue:work --queue=exports,default
You can also dispatch exports directly:
use Filament\AdvancedExport\Jobs\ProcessExportJob;
ProcessExportJob::dispatch(
modelClass: Cliente::class,
filters: $activeFilters,
fileName: 'clientes_export_2024.xlsx',
viewName: 'exports.clientes-excel-advanced',
columnsConfig: $columnsConfig,
orderColumn: 'created_at',
orderDirection: 'desc',
relationships: ['tipoCliente', 'provincia'],
userId: auth()->id() // Required for notifications
);
If you use a custom user model, configure it in config/advanced-export.php:
'user_model' => App\Models\User::class,
Background exports fully support all filter types:
| Filter Type | Automatically Handled |
|-------------|----------------------|
| Direct columns | status, type |
| Relationship filters | insurer → insurer_id |
| Date ranges | created_at[from/until] |
| Multiple values | status[values][0,1,2] |
The ProcessExportJob automatically resolves relationship filter names to their actual column names (e.g., insurer → insurer_id).
The package uses Blade views to render Excel exports. You can customize these views to format data, add styling, or handle special fields.
| Type | Generated File | Purpose |
|------|----------------|---------|
| Simple | {table}-excel.blade.php | Exports all model attributes automatically |
| Advanced | {table}-excel-advanced.blade.php | Exports only user-selected columns with custom titles |
Both views receive these variables:
| Variable | Type | Description |
|----------|------|-------------|
| ${tableName} | Collection | The records to export (e.g., $declarations) |
| $columnsConfig | array | User-selected columns with titles (advanced only) |
@php use Carbon\Carbon; @endphp
<table>
<thead>
<tr>
@foreach($columnsConfig as $columnConfig)
<th>{{ $columnConfig['title'] ?? 'Untitled' }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach($declarations as $declaration)
<tr>
@foreach($columnsConfig as $columnConfig)
@php $field = $columnConfig['field'] ?? ''; @endphp
<td>
@switch($field)
{{-- Custom field handling here --}}
@default
{{ $declaration->{$field} ?? '-' }}
@endswitch
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
Use @switch statements to format specific fields:
@switch($field)
{{-- Date formatting --}}
@case('created_at')
{{ $record->created_at?->format('d/m/Y H:i') ?? '-' }}
@break
{{-- Relationship data --}}
@case('insurer')
{{ $record->insurer->name ?? '-' }}
@break
{{-- Currency formatting --}}
@case('amount')
{{ number_format($record->amount, 2, ',', '.') }}
@break
{{-- Boolean values --}}
@case('is_active')
{{ $record->is_active ? 'Yes' : 'No' }}
@break
{{-- Enum/Status with translation --}}
@case('status')
{{ __("statuses.{$record->status}") }}
@break
{{-- Nested relationships --}}
@case('client.province')
{{ $record->client?->province?->name ?? '-' }}
@break
@default
{{ $record->{$field} ?? '-' }}
@endswitch
Here's a full example for a declarations export:
{{-- resources/views/exports/declarations-excel-advanced.blade.php --}}
@php use Carbon\Carbon; @endphp
<table>
<thead>
<tr>
@foreach($columnsConfig as $columnConfig)
<th>{{ $columnConfig['title'] ?? __('advanced-export::messages.undefined_title') }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach($declarations as $declaration)
<tr>
@foreach($columnsConfig as $columnConfig)
@php $field = $columnConfig['field'] ?? ''; @endphp
<td>
@switch($field)
@case('id')
{{ $declaration->id ?? '-' }}
@break
@case('declaration_number')
{{ $declaration->declaration_number ?? '-' }}
@break
@case('insurer')
{{ $declaration->insurer->name ?? '-' }}
@break
@case('cif_value')
{{ number_format($declaration->cif_value ?? 0, 2, ',', '.') }}
@break
@case('status')
{{ ucfirst($declaration->status ?? '-') }}
@break
@case('created_at')
{{ $declaration->created_at?->format('d/m/Y H:i') ?? '-' }}
@break
@default
{{ $declaration->{$field} ?? '-' }}
@endswitch
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
If you don't need custom formatting, enable the package's default views:
// config/advanced-export.php
'views' => [
'use_package_views' => true,
],
The default views automatically:
config('advanced-export.date_format')-Maatwebsite Excel supports basic HTML styling:
<table>
<thead>
<tr style="background-color: #4CAF50; color: white; font-weight: bold;">
@foreach($columnsConfig as $columnConfig)
<th>{{ $columnConfig['title'] }}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach($records as $record)
<tr style="{{ $loop->even ? 'background-color: #f2f2f2;' : '' }}">
{{-- cells --}}
</tr>
@endforeach
</tbody>
</table>
Override trait methods to customize appearance:
class ListClientes extends ListRecords
{
use HasAdvancedExport;
protected function getExportActionLabel(): string
{
return 'Download Excel';
}
protected function getExportActionIcon(): string
{
return 'heroicon-o-document-arrow-down';
}
protected function getExportActionColor(): string
{
return 'primary';
}
protected function getExportModalHeading(): string
{
return 'Configure Your Export';
}
}
If you use FilamentShield, the export button can be controlled per role/resource.
use Filament\AdvancedExport\Concerns\HasExportPermission;
class TitularResource extends Resource
{
use HasExportPermission;
// ...
}
This registers export as a permission prefix. When you run php artisan shield:generate, Shield will create Export:Titular automatically.
The export button will only be visible to users with the Export:{Resource} permission. Without Shield installed, the button is visible to everyone.
HasExportPermission trait on the Resource → tells Shield to generate the export permissionHasAdvancedExport trait on the ListRecords → checks if user has the permission before showing the buttonPlease see CHANGELOG for more information on what has changed recently.
Contributions are welcome! Please see CONTRIBUTING for details.
If you discover any security-related issues, please email [email protected] instead of using the issue tracker.
The MIT License (MIT). Please see License File for more information.