filament/actions
Filament Actions adds reusable, customizable UI actions to Filament admin panels. Define buttons, modals, confirmations, and forms with a fluent API, then run callbacks, validations, and notifications consistently across tables, resources, and pages.
title: Export action
import Aside from "@components/Aside.astro" import AutoScreenshot from "@components/AutoScreenshot.astro" import UtilityInjection from "@components/UtilityInjection.astro"
Filament includes an action that is able to export rows to a CSV or XLSX file. When the trigger button is clicked, a modal asks for the columns that they want to export, and what they should be labeled. This feature uses job batches and database notifications, so you need to publish those migrations from Laravel. Also, you need to publish the migrations for tables that Filament uses to store information about exports:
php artisan make:queue-batches-table
php artisan make:notifications-table
php artisan vendor:publish --tag=filament-actions-migrations
php artisan migrate
If you'd like to receive export notifications in a panel, you can enable them in the panel configuration.
You may use the ExportAction like so:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
If you want to add this action to the header of a table, you may do so like this:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->headerActions([
ExportAction::make()
->exporter(ProductExporter::class),
]);
}
Or if you want to add it as a table bulk action, so that the user can choose which rows to export, they can use Filament\Actions\ExportBulkAction:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->toolbarActions([
ExportBulkAction::make()
->exporter(ProductExporter::class),
]);
}
The "exporter" class needs to be created to tell Filament how to export each row.
To create an exporter class for a model, you may use the make:filament-exporter command, passing the name of a model:
php artisan make:filament-exporter Product
This will create a new class in the app/Filament/Exports directory. You now need to define the columns that can be exported.
If you'd like to save time, Filament can automatically generate the columns for you, based on your model's database columns, using --generate:
php artisan make:filament-exporter Product --generate
To define the columns that can be exported, you need to override the getColumns() method on your exporter class, returning an array of ExportColumn objects:
use Filament\Actions\Exports\ExportColumn;
public static function getColumns(): array
{
return [
ExportColumn::make('name'),
ExportColumn::make('sku')
->label('SKU'),
ExportColumn::make('price'),
];
}
The label for each column will be generated automatically from its name, but you can override it by calling the label() method:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('sku')
->label('SKU')
By default, all columns will be selected when the user is asked which columns they would like to export. You can customize the default selection state for a column with the enabledByDefault() method:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->enabledByDefault(false)
You can use the enableVisibleTableColumnsByDefault() method on the ExportAction to enable only the columns that are currently visible in the table by default. Columns that use enabledByDefault(false) will also be disabled by default:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->enableVisibleTableColumnsByDefault()
By default, the column selection form uses a single column layout. You can change this using the columnMappingColumns() method, passing the number of columns you would like to use for the layout on large screens:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->columnMappingColumns(3)
This will display the column selection checkboxes and label inputs in a 3-column layout, making better use of available space when you have many exportable columns. Note that while there will be three columns in the layout on large screens, the layout will still be responsive and there will be fewer columns displayed on smaller screens.
By default, user will be asked which columns they would like to export. You can disable this functionality using columnMapping(false):
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->columnMapping(false)
Sometimes you need to calculate the state of a column, instead of directly reading it from a database column.
By passing a callback function to the state() method, you can customize the returned state for that column based on the $record:
use App\Models\Order;
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('amount_including_vat')
->state(function (Order $record): float {
return $record->amount * (1 + $record->vat_rate);
})
<UtilityInjection set="exportColumns" version="5.x">As well as $record, the state() function can inject various utilities as parameters.</UtilityInjection>
You may instead pass a custom formatting callback to formatStateUsing(), which accepts the $state of the cell, and optionally the Eloquent $record:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('status')
->formatStateUsing(fn (string $state): string => __("statuses.{$state}"))
<UtilityInjection set="exportColumns" version="5.x" extras="State;;mixed;;$state;;The state to format.">As well as $state, the formatStateUsing() function can inject various utilities as parameters.</UtilityInjection>
If there are multiple values in the column, the function will be called for each value.
You may limit() the length of the cell's value:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->limit(50)
<UtilityInjection set="exportColumns" version="5.x">As well as allowing a static value, the limit() method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
You may limit the number of words() displayed in the cell:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->words(10)
<UtilityInjection set="exportColumns" version="5.x">As well as allowing a static value, the words() method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
You may add a prefix() or suffix() to the cell's value:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('domain')
->prefix('https://')
->suffix('.com')
<UtilityInjection set="exportColumns" version="5.x">As well as allowing static values, the prefix() and suffix() methods also accept functions to dynamically calculate them. You can inject various utilities into the functions as parameters.</UtilityInjection>
By default, if there are multiple values in the column, they will be comma-separated. You may use the listAsJson() method to list them as a JSON array instead:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('tags')
->listAsJson()
You may use "dot notation" to access columns within relationships. The name of the relationship comes first, followed by a period, followed by the name of the column to display:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('author.name')
If you wish to count the number of related records in a column, you may use the counts() method:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('users_count')
->counts('users')
In this example, users is the name of the relationship to count from. The name of the column must be users_count, as this is the convention that Laravel uses for storing the result.
If you'd like to scope the relationship before counting, you can pass an array to the method, where the key is the relationship name and the value is the function to scope the Eloquent query with:
use Filament\Actions\Exports\ExportColumn;
use Illuminate\Database\Eloquent\Builder;
ExportColumn::make('users_count')
->counts([
'users' => fn (Builder $query) => $query->where('is_active', true),
])
If you simply wish to indicate whether related records exist in a column, you may use the exists() method:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('users_exists')
->exists('users')
In this example, users is the name of the relationship to check for existence. The name of the column must be users_exists, as this is the convention that Laravel uses for storing the result.
If you'd like to scope the relationship before checking existence, you can pass an array to the method, where the key is the relationship name and the value is the function to scope the Eloquent query with:
use Filament\Actions\Exports\ExportColumn;
use Illuminate\Database\Eloquent\Builder;
ExportColumn::make('users_exists')
->exists([
'users' => fn (Builder $query) => $query->where('is_active', true),
])
Filament provides several methods for aggregating a relationship field, including avg(), max(), min() and sum(). For instance, if you wish to show the average of a field on all related records in a column, you may use the avg() method:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('users_avg_age')
->avg('users', 'age')
In this example, users is the name of the relationship, while age is the field that is being averaged. The name of the column must be users_avg_age, as this is the convention that Laravel uses for storing the result.
If you'd like to scope the relationship before aggregating, you can pass an array to the method, where the key is the relationship name and the value is the function to scope the Eloquent query with:
use Filament\Actions\Exports\ExportColumn;
use Illuminate\Database\Eloquent\Builder;
ExportColumn::make('users_avg_age')
->avg([
'users' => fn (Builder $query) => $query->where('is_active', true),
], 'age')
By default, the export action will generate both CSV and XLSX formats and allow user to choose between them in the notification. You can use the ExportFormat enum to customize this, by passing an array of formats to the formats() method on the action:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
use Filament\Actions\Exports\Enums\ExportFormat;
ExportAction::make()
->exporter(ProductExporter::class)
->formats([
ExportFormat::Csv,
])
// or
->formats([
ExportFormat::Xlsx,
])
// or
->formats([
ExportFormat::Xlsx,
ExportFormat::Csv,
])
Alternatively, you can override the getFormats() method on the exporter class, which will set the default formats for all actions that use that exporter:
use Filament\Actions\Exports\Enums\ExportFormat;
public function getFormats(): array
{
return [
ExportFormat::Csv,
];
}
By default, if you are using the ExportAction with a table, the action will use the table's currently filtered and sorted query to export the data. If you don't have a table, it will use the model's default query. To modify the query builder before exporting, you can use the modifyQueryUsing() method on the action:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
use Illuminate\Database\Eloquent\Builder;
ExportAction::make()
->exporter(ProductExporter::class)
->modifyQueryUsing(fn (Builder $query) => $query->where('is_active', true))
You may inject the $options argument into the function, which is an array of options for that export:
use App\Filament\Exports\ProductExporter;
use Illuminate\Database\Eloquent\Builder;
ExportAction::make()
->exporter(ProductExporter::class)
->modifyQueryUsing(fn (Builder $query, array $options) => $query->where('is_active', $options['isActive'] ?? true))
Alternatively, you can override the modifyQuery() method on the exporter class, which will modify the query for all actions that use that exporter:
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphTo;
public static function modifyQuery(Builder $query): Builder
{
return $query->with([
'purchasable' => fn (MorphTo $morphTo) => $morphTo->morphWith([
ProductPurchase::class => ['product'],
ServicePurchase::class => ['service'],
Subscription::class => ['plan'],
]),
]);
}
By default, exported files will be uploaded to the storage disk defined in the configuration file, which is public by default. You can set the FILESYSTEM_DISK environment variable to change this.
While using the public disk a good default for many parts of Filament, using it for exports would result in exported files being stored in a public location. As such, if the default filesystem disk is public and a local disk exists in your config/filesystems.php, Filament will use the local disk for exports instead. If you override the disk to be public for an ExportAction or inside an exporter class, Filament will use that.
In production, you should use a disk such as s3 with a private access policy, to prevent unauthorized access to the exported files.
If you want to use a different disk for a specific export, you can pass the disk name to the disk() method on the action:
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->fileDisk('s3')
You may set the disk for all export actions at once in the boot() method of a service provider such as AppServiceProvider:
use Filament\Actions\ExportAction;
ExportAction::configureUsing(fn (ExportAction $action) => $action->fileDisk('s3'));
Alternatively, you can override the getFileDisk() method on the exporter class, returning the name of the disk:
public function getFileDisk(): string
{
return 's3';
}
Export files that are created are the developer's responsibility to delete if they wish. Filament does not delete these files in case the exports need to be downloaded again at a later date.
By default, exported files are given a name generated based on the export's ID and type. You can customize the file name by using the fileName() method on the action:
use Filament\Actions\ExportAction;
use Filament\Actions\Exports\Models\Export;
ExportAction::make()
->exporter(ProductExporter::class)
->fileName(fn (Export $export): string => "products-{$export->getKey()}")
Alternatively, you can override the getFileName() method on the exporter class and return a custom string:
use Filament\Actions\Exports\Models\Export;
public function getFileName(Export $export): string
{
return "products-{$export->getKey()}";
}
The export action can render extra form components that the user can interact with when exporting a CSV. This can be useful to allow the user to customize the behavior of the exporter. For instance, you might want a user to be able to choose the format of specific columns when exporting. To do this, you can return options form components from the getOptionsFormComponents() method on your exporter class:
use Filament\Forms\Components\TextInput;
public static function getOptionsFormComponents(): array
{
return [
TextInput::make('descriptionLimit')
->label('Limit the length of the description column content')
->integer(),
];
}
Alternatively, you can pass a set of static options to the exporter through the options() method on the action:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->options([
'descriptionLimit' => 250,
])
<UtilityInjection set="actions" version="5.x">As well as allowing a static value, the options() method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.</UtilityInjection>
Now, you can access the data from these options inside the exporter class, by injecting the $options argument into any closure function. For example, you might want to use it inside formatStateUsing() to format a column's value:
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->formatStateUsing(function (string $state, array $options): string {
return (string) str($state)->limit($options['descriptionLimit'] ?? 100);
})
Alternatively, since the $options argument is passed to all closure functions, you can access it inside limit():
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->limit(fn (array $options): int => $options['descriptionLimit'] ?? 100)
By default, the exports table has a user_id column. That column is constrained to the users table:
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
In the Export model, the user() relationship is defined as a BelongsTo relationship to the App\Models\User model. If the App\Models\User model does not exist, or you want to use a different one, you can bind a new Authenticatable model to the container in a service provider's register() method:
use App\Models\Admin;
use Illuminate\Contracts\Auth\Authenticatable;
$this->app->bind(Authenticatable::class, Admin::class);
If your authenticatable model uses a different table to users, you should pass that table name to constrained():
$table->foreignId('user_id')->constrained('admins')->cascadeOnDelete();
If you want to associate exports with multiple user models, you can use a polymorphic MorphTo relationship instead. To do this, you need to replace the user_id column in the exports table:
$table->morphs('user');
Then, in a service provider's boot() method, you should call Export::polymorphicUserRelationship() to swap the user() relationship on the Export model to a MorphTo relationship:
use Filament\Actions\Exports\Models\Export;
Export::polymorphicUserRelationship();
To prevent server overload, you may wish to limit the maximum number of rows that can be exported from one CSV file. You can do this by calling the maxRows() method on the action:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->maxRows(100000)
Filament will chunk the CSV, and process each chunk in a different queued job. By default, chunks are 100 rows at a time. You can change this by calling the chunkSize() method on the action:
use App\Filament\Exports\ProductExporter;
use Filament\Actions\ExportAction;
ExportAction::make()
->exporter(ProductExporter::class)
->chunkSize(250)
<UtilityInjection set="actions" version="5.x">As well as allowing a static value, the `chunkSiz.....
How can I help you explore Laravel packages today?