[增添]添加了datasource的setting数据库以及默认值

This commit is contained in:
makotocc0107
2024-08-27 09:57:44 +08:00
parent d111dfaea4
commit 72eb990970
10955 changed files with 978898 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
---
title: Create action
---
## Overview
Filament includes a prebuilt action that is able to create Eloquent records. When the trigger button is clicked, a modal will open with a form inside. The user fills the form, and that data is validated and saved into the database. You may use it like so:
```php
use Filament\Actions\CreateAction;
use Filament\Forms\Components\TextInput;
CreateAction::make()
->model(Post::class)
->form([
TextInput::make('title')
->required()
->maxLength(255),
// ...
])
```
If you want to add this action to the header of a table instead, you can use `Filament\Tables\Actions\CreateAction`:
```php
use Filament\Forms\Components\TextInput;
use Filament\Tables\Actions\CreateAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->headerActions([
CreateAction::make()
->form([
TextInput::make('title')
->required()
->maxLength(255),
// ...
]),
]);
}
```
## Customizing data before saving
Sometimes, you may wish to modify form data before it is finally saved to the database. To do this, you may use the `mutateFormDataUsing()` method, which has access to the `$data` as an array, and returns the modified version:
```php
CreateAction::make()
->mutateFormDataUsing(function (array $data): array {
$data['user_id'] = auth()->id();
return $data;
})
```
## Customizing the creation process
You can tweak how the record is created with the `using()` method:
```php
use Illuminate\Database\Eloquent\Model;
CreateAction::make()
->using(function (array $data, string $model): Model {
return $model::create($data);
})
```
`$model` is the class name of the model, but you can replace this with your own hard-coded class if you wish.
## Redirecting after creation
You may set up a custom redirect when the form is submitted using the `successRedirectUrl()` method:
```php
CreateAction::make()
->successRedirectUrl(route('posts.list'))
```
If you want to redirect using the created record, use the `$record` parameter:
```php
use Illuminate\Database\Eloquent\Model;
CreateAction::make()
->successRedirectUrl(fn (Model $record): string => route('posts.edit', [
'post' => $record,
]))
```
## Customizing the save notification
When the record is successfully created, a notification is dispatched to the user, which indicates the success of their action.
To customize the title of this notification, use the `successNotificationTitle()` method:
```php
CreateAction::make()
->successNotificationTitle('User registered')
```
You may customize the entire notification using the `successNotification()` method:
```php
use Filament\Notifications\Notification;
CreateAction::make()
->successNotification(
Notification::make()
->success()
->title('User registered')
->body('The user has been created successfully.'),
)
```
To disable the notification altogether, use the `successNotification(null)` method:
```php
CreateAction::make()
->successNotification(null)
```
## Lifecycle hooks
Hooks may be used to execute code at various points within the action's lifecycle, like before a form is saved.
There are several available hooks:
```php
CreateAction::make()
->beforeFormFilled(function () {
// Runs before the form fields are populated with their default values.
})
->afterFormFilled(function () {
// Runs after the form fields are populated with their default values.
})
->beforeFormValidated(function () {
// Runs before the form fields are validated when the form is submitted.
})
->afterFormValidated(function () {
// Runs after the form fields are validated when the form is submitted.
})
->before(function () {
// Runs before the form fields are saved to the database.
})
->after(function () {
// Runs after the form fields are saved to the database.
})
```
## Halting the creation process
At any time, you may call `$action->halt()` from inside a lifecycle hook or mutation method, which will halt the entire creation process:
```php
use App\Models\Post;
use Filament\Notifications\Actions\Action;
use Filament\Notifications\Notification;
CreateAction::make()
->before(function (CreateAction $action, Post $record) {
if (! $record->team->subscribed()) {
Notification::make()
->warning()
->title('You don\'t have an active subscription!')
->body('Choose a plan to continue.')
->persistent()
->actions([
Action::make('subscribe')
->button()
->url(route('subscribe'), shouldOpenInNewTab: true),
])
->send();
$action->halt();
}
})
```
If you'd like the action modal to close too, you can completely `cancel()` the action instead of halting it:
```php
$action->cancel();
```
## Using a wizard
You may easily transform the creation process into a multistep wizard. Instead of using a `form()`, define a `steps()` array and pass your `Step` objects:
```php
use Filament\Forms\Components\MarkdownEditor;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Forms\Components\Wizard\Step;
CreateAction::make()
->steps([
Step::make('Name')
->description('Give the category a unique name')
->schema([
TextInput::make('name')
->required()
->live()
->afterStateUpdated(fn ($state, callable $set) => $set('slug', Str::slug($state))),
TextInput::make('slug')
->disabled()
->required()
->unique(Category::class, 'slug'),
])
->columns(2),
Step::make('Description')
->description('Add some extra details')
->schema([
MarkdownEditor::make('description'),
]),
Step::make('Visibility')
->description('Control who can view it')
->schema([
Toggle::make('is_visible')
->label('Visible to customers.')
->default(true),
]),
])
```
Now, create a new record to see your wizard in action! Edit will still use the form defined within the resource class.
If you'd like to allow free navigation, so all the steps are skippable, use the `skippableSteps()` method:
```php
CreateAction::make()
->steps([
// ...
])
->skippableSteps()
```
## Disabling create another
If you'd like to remove the "create another" button from the modal, you can use the `createAnother(false)` method:
```php
CreateAction::make()
->createAnother(false)
```

View File

@@ -0,0 +1,200 @@
---
title: Edit action
---
## Overview
Filament includes a prebuilt action that is able to edit Eloquent records. When the trigger button is clicked, a modal will open with a form inside. The user fills the form, and that data is validated and saved into the database. You may use it like so:
```php
use Filament\Actions\EditAction;
use Filament\Forms\Components\TextInput;
EditAction::make()
->record($this->post)
->form([
TextInput::make('title')
->required()
->maxLength(255),
// ...
])
```
If you want to edit table rows, you can use the `Filament\Tables\Actions\EditAction` instead:
```php
use Filament\Forms\Components\TextInput;
use Filament\Tables\Actions\EditAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
EditAction::make()
->form([
TextInput::make('title')
->required()
->maxLength(255),
// ...
]),
]);
}
```
## Customizing data before filling the form
You may wish to modify the data from a record before it is filled into the form. To do this, you may use the `mutateRecordDataUsing()` method to modify the `$data` array, and return the modified version before it is filled into the form:
```php
EditAction::make()
->mutateRecordDataUsing(function (array $data): array {
$data['user_id'] = auth()->id();
return $data;
})
```
## Customizing data before saving
Sometimes, you may wish to modify form data before it is finally saved to the database. To do this, you may use the `mutateFormDataUsing()` method, which has access to the `$data` as an array, and returns the modified version:
```php
EditAction::make()
->mutateFormDataUsing(function (array $data): array {
$data['last_edited_by_id'] = auth()->id();
return $data;
})
```
## Customizing the saving process
You can tweak how the record is updated with the `using()` method:
```php
use Illuminate\Database\Eloquent\Model;
EditAction::make()
->using(function (Model $record, array $data): Model {
$record->update($data);
return $record;
})
```
## Redirecting after saving
You may set up a custom redirect when the form is submitted using the `successRedirectUrl()` method:
```php
EditAction::make()
->successRedirectUrl(route('posts.list'))
```
If you want to redirect using the created record, use the `$record` parameter:
```php
use Illuminate\Database\Eloquent\Model;
EditAction::make()
->successRedirectUrl(fn (Model $record): string => route('posts.view', [
'post' => $record,
]))
```
## Customizing the save notification
When the record is successfully updated, a notification is dispatched to the user, which indicates the success of their action.
To customize the title of this notification, use the `successNotificationTitle()` method:
```php
EditAction::make()
->successNotificationTitle('User updated')
```
You may customize the entire notification using the `successNotification()` method:
```php
use Filament\Notifications\Notification;
EditAction::make()
->successNotification(
Notification::make()
->success()
->title('User updated')
->body('The user has been saved successfully.'),
)
```
To disable the notification altogether, use the `successNotification(null)` method:
```php
EditAction::make()
->successNotification(null)
```
## Lifecycle hooks
Hooks may be used to execute code at various points within the action's lifecycle, like before a form is saved.
There are several available hooks:
```php
EditAction::make()
->beforeFormFilled(function () {
// Runs before the form fields are populated from the database.
})
->afterFormFilled(function () {
// Runs after the form fields are populated from the database.
})
->beforeFormValidated(function () {
// Runs before the form fields are validated when the form is saved.
})
->afterFormValidated(function () {
// Runs after the form fields are validated when the form is saved.
})
->before(function () {
// Runs before the form fields are saved to the database.
})
->after(function () {
// Runs after the form fields are saved to the database.
})
```
## Halting the saving process
At any time, you may call `$action->halt()` from inside a lifecycle hook or mutation method, which will halt the entire saving process:
```php
use App\Models\Post;
use Filament\Notifications\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Tables\Actions\EditAction;
EditAction::make()
->before(function (EditAction $action, Post $record) {
if (! $record->team->subscribed()) {
Notification::make()
->warning()
->title('You don\'t have an active subscription!')
->body('Choose a plan to continue.')
->persistent()
->actions([
Action::make('subscribe')
->button()
->url(route('subscribe'), shouldOpenInNewTab: true),
])
->send();
$action->halt();
}
})
```
If you'd like the action modal to close too, you can completely `cancel()` the action instead of halting it:
```php
$action->cancel();
```

View File

@@ -0,0 +1,56 @@
---
title: View action
---
## Overview
Filament includes a prebuilt action that is able to view Eloquent records. When the trigger button is clicked, a modal will open with information inside. Filament uses form fields to structure this information. All form fields are disabled, so they are not editable by the user. You may use it like so:
```php
use Filament\Actions\ViewAction;
use Filament\Forms\Components\TextInput;
ViewAction::make()
->record($this->post)
->form([
TextInput::make('title')
->required()
->maxLength(255),
// ...
])
```
If you want to view table rows, you can use the `Filament\Tables\Actions\ViewAction` instead:
```php
use Filament\Forms\Components\TextInput;
use Filament\Tables\Actions\ViewAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
ViewAction::make()
->form([
TextInput::make('title')
->required()
->maxLength(255),
// ...
]),
]);
}
```
## Customizing data before filling the form
You may wish to modify the data from a record before it is filled into the form. To do this, you may use the `mutateRecordDataUsing()` method to modify the `$data` array, and return the modified version before it is filled into the form:
```php
ViewAction::make()
->mutateRecordDataUsing(function (array $data): array {
$data['user_id'] = auth()->id();
return $data;
})
```

View File

@@ -0,0 +1,93 @@
---
title: Delete action
---
## Overview
Filament includes a prebuilt action that is able to delete Eloquent records. When the trigger button is clicked, a modal asks the user for confirmation. You may use it like so:
```php
use Filament\Actions\DeleteAction;
DeleteAction::make()
->record($this->post)
```
If you want to delete table rows, you can use the `Filament\Tables\Actions\DeleteAction` instead, or `Filament\Tables\Actions\DeleteBulkAction` to delete multiple at once:
```php
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\DeleteAction;
use Filament\Tables\Actions\DeleteBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
DeleteAction::make(),
// ...
])
->bulkActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
// ...
]),
]);
}
```
## Redirecting after deleting
You may set up a custom redirect when the form is submitted using the `successRedirectUrl()` method:
```php
DeleteAction::make()
->successRedirectUrl(route('posts.list'))
```
## Customizing the delete notification
When the record is successfully deleted, a notification is dispatched to the user, which indicates the success of their action.
To customize the title of this notification, use the `successNotificationTitle()` method:
```php
DeleteAction::make()
->successNotificationTitle('User deleted')
```
You may customize the entire notification using the `successNotification()` method:
```php
use Filament\Notifications\Notification;
DeleteAction::make()
->successNotification(
Notification::make()
->success()
->title('User deleted')
->body('The user has been deleted successfully.'),
)
```
To disable the notification altogether, use the `successNotification(null)` method:
```php
DeleteAction::make()
->successNotification(null)
```
## Lifecycle hooks
You can use the `before()` and `after()` methods to execute code before and after a record is deleted:
```php
DeleteAction::make()
->before(function () {
// ...
})
->after(function () {
// ...
})
```

View File

@@ -0,0 +1,151 @@
---
title: Replicate action
---
## Overview
Filament includes a prebuilt action that is able to [replicate](https://laravel.com/docs/eloquent#replicating-models) Eloquent records. You may use it like so:
```php
use Filament\Actions\ReplicateAction;
ReplicateAction::make()
->record($this->post)
```
If you want to replicate table rows, you can use the `Filament\Tables\Actions\ReplicateAction` instead:
```php
use Filament\Tables\Actions\ReplicateAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
ReplicateAction::make(),
// ...
]);
}
```
## Excluding attributes
The `excludeAttributes()` method is used to instruct the action which columns should be excluded from replication:
```php
ReplicateAction::make()
->excludeAttributes(['slug'])
```
## Customizing data before filling the form
You may wish to modify the data from a record before it is filled into the form. To do this, you may use the `mutateRecordDataUsing()` method to modify the `$data` array, and return the modified version before it is filled into the form:
```php
ReplicateAction::make()
->mutateRecordDataUsing(function (array $data): array {
$data['user_id'] = auth()->id();
return $data;
})
```
## Redirecting after replication
You may set up a custom redirect when the form is submitted using the `successRedirectUrl()` method:
```php
ReplicateAction::make()
->successRedirectUrl(route('posts.list'))
```
If you want to redirect using the replica, use the `$replica` parameter:
```php
use Illuminate\Database\Eloquent\Model;
ReplicateAction::make()
->successRedirectUrl(fn (Model $replica): string => route('posts.edit', [
'post' => $replica,
]))
```
## Customizing the replicate notification
When the record is successfully replicated, a notification is dispatched to the user, which indicates the success of their action.
To customize the title of this notification, use the `successNotificationTitle()` method:
```php
ReplicateAction::make()
->successNotificationTitle('Category replicated')
```
You may customize the entire notification using the `successNotification()` method:
```php
use Filament\Notifications\Notification;
ReplicateAction::make()
->successNotification(
Notification::make()
->success()
->title('Category replicated')
->body('The category has been replicated successfully.'),
)
```
## Lifecycle hooks
Hooks may be used to execute code at various points within the action's lifecycle, like before the replica is saved.
```php
use Illuminate\Database\Eloquent\Model;
ReplicateAction::make()
->before(function () {
// Runs before the record has been replicated.
})
->beforeReplicaSaved(function (Model $replica): void {
// Runs after the record has been replicated but before it is saved to the database.
})
->after(function (Model $replica): void {
// Runs after the replica has been saved to the database.
})
```
## Halting the replication process
At any time, you may call `$action->halt()` from inside a lifecycle hook, which will halt the entire replication process:
```php
use App\Models\Post;
use Filament\Notifications\Actions\Action;
use Filament\Notifications\Notification;
ReplicateAction::make()
->before(function (ReplicateAction $action, Post $record) {
if (! $record->team->subscribed()) {
Notification::make()
->warning()
->title('You don\'t have an active subscription!')
->body('Choose a plan to continue.')
->persistent()
->actions([
Action::make('subscribe')
->button()
->url(route('subscribe'), shouldOpenInNewTab: true),
])
->send();
$action->halt();
}
})
```
If you'd like the action modal to close too, you can completely `cancel()` the action instead of halting it:
```php
$action->cancel();
```

View File

@@ -0,0 +1,93 @@
---
title: Force-delete action
---
## Overview
Filament includes a prebuilt action that is able to force-delete [soft deleted](https://laravel.com/docs/eloquent#soft-deleting) Eloquent records. When the trigger button is clicked, a modal asks the user for confirmation. You may use it like so:
```php
use Filament\Actions\ForceDeleteAction;
ForceDeleteAction::make()
->record($this->post)
```
If you want to force-delete table rows, you can use the `Filament\Tables\Actions\ForceDeleteAction` instead, or `Filament\Tables\Actions\ForceDeleteBulkAction` to force-delete multiple at once:
```php
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\ForceDeleteAction;
use Filament\Tables\Actions\ForceDeleteBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
ForceDeleteAction::make(),
// ...
])
->bulkActions([
BulkActionGroup::make([
ForceDeleteBulkAction::make(),
// ...
]),
]);
}
```
## Redirecting after force-deleting
You may set up a custom redirect when the form is submitted using the `successRedirectUrl()` method:
```php
ForceDeleteAction::make()
->successRedirectUrl(route('posts.list'))
```
## Customizing the force-delete notification
When the record is successfully force-deleted, a notification is dispatched to the user, which indicates the success of their action.
To customize the title of this notification, use the `successNotificationTitle()` method:
```php
ForceDeleteAction::make()
->successNotificationTitle('User force-deleted')
```
You may customize the entire notification using the `successNotification()` method:
```php
use Filament\Notifications\Notification;
ForceDeleteAction::make()
->successNotification(
Notification::make()
->success()
->title('User force-deleted')
->body('The user has been force-deleted successfully.'),
)
```
To disable the notification altogether, use the `successNotification(null)` method:
```php
ForceDeleteAction::make()
->successNotification(null)
```
## Lifecycle hooks
You can use the `before()` and `after()` methods to execute code before and after a record is force-deleted:
```php
ForceDeleteAction::make()
->before(function () {
// ...
})
->after(function () {
// ...
})
```

View File

@@ -0,0 +1,93 @@
---
title: Restore action
---
## Overview
Filament includes a prebuilt action that is able to restore [soft deleted](https://laravel.com/docs/eloquent#soft-deleting) Eloquent records. When the trigger button is clicked, a modal asks the user for confirmation. You may use it like so:
```php
use Filament\Actions\RestoreAction;
RestoreAction::make()
->record($this->post)
```
If you want to restore table rows, you can use the `Filament\Tables\Actions\RestoreAction` instead, or `Filament\Tables\Actions\RestoreBulkAction` to restore multiple at once:
```php
use Filament\Tables\Actions\BulkActionGroup;
use Filament\Tables\Actions\RestoreAction;
use Filament\Tables\Actions\RestoreBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->actions([
RestoreAction::make(),
// ...
])
->bulkActions([
BulkActionGroup::make([
RestoreBulkAction::make(),
// ...
]),
]);
}
```
## Redirecting after restoring
You may set up a custom redirect when the form is submitted using the `successRedirectUrl()` method:
```php
RestoreAction::make()
->successRedirectUrl(route('posts.list'))
```
## Customizing the restore notification
When the record is successfully restored, a notification is dispatched to the user, which indicates the success of their action.
To customize the title of this notification, use the `successNotificationTitle()` method:
```php
RestoreAction::make()
->successNotificationTitle('User restored')
```
You may customize the entire notification using the `successNotification()` method:
```php
use Filament\Notifications\Notification;
RestoreAction::make()
->successNotification(
Notification::make()
->success()
->title('User restored')
->body('The user has been restored successfully.'),
)
```
To disable the notification altogether, use the `successNotification(null)` method:
```php
RestoreAction::make()
->successNotification(null)
```
## Lifecycle hooks
You can use the `before()` and `after()` methods to execute code before and after a record is restored:
```php
RestoreAction::make()
->before(function () {
// ...
})
->after(function () {
// ...
})
```

View File

@@ -0,0 +1,829 @@
---
title: Import action
---
## Overview
Filament v3.1 introduced a prebuilt action that is able to import rows from a CSV. When the trigger button is clicked, a modal asks the user for a file. Once they upload one, they are able to map each column in the CSV to a real column in the database. If any rows fail validation, they will be compiled into a downloadable CSV for the user to review after the rest of the rows have been imported. Users can also download an example CSV file containing all the columns that can be imported.
This feature uses [job batches](https://laravel.com/docs/queues#job-batching) and [database notifications](../../notifications/database-notifications#overview), 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 imports:
```bash
# Laravel 11 and higher
php artisan make:queue-batches-table
php artisan make:notifications-table
# Laravel 10
php artisan queue:batches-table
php artisan notifications:table
php artisan vendor:publish --tag=filament-actions-migrations
php artisan migrate
```
> If you're using PostgreSQL, make sure that the `data` column in the notifications migration is using `json()`: `$table->json('data')`.
> If you're using UUIDs for your `User` model, make sure that your `notifiable` column in the notifications migration is using `uuidMorphs()`: `$table->uuidMorphs('notifiable')`.
You may use the `ImportAction` like so:
```php
use App\Filament\Imports\ProductImporter;
use Filament\Actions\ImportAction;
ImportAction::make()
->importer(ProductImporter::class)
```
If you want to add this action to the header of a table instead, you can use `Filament\Tables\Actions\ImportAction`:
```php
use App\Filament\Imports\ProductImporter;
use Filament\Tables\Actions\ImportAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->headerActions([
ImportAction::make()
->importer(ProductImporter::class)
]);
}
```
The ["importer" class needs to be created](#creating-an-importer) to tell Filament how to import each row of the CSV.
If you have more than one `ImportAction` in the same place, you should give each a unique name in the `make()` method:
```php
ImportAction::make('importProducts')
->importer(ProductImporter::class)
ImportAction::make('importBrands')
->importer(BrandImporter::class)
```
## Creating an importer
To create an importer class for a model, you may use the `make:filament-importer` command, passing the name of a model:
```bash
php artisan make:filament-importer Product
```
This will create a new class in the `app/Filament/Imports` directory. You now need to define the [columns](#defining-importer-columns) that can be imported.
### Automatically generating importer columns
If you'd like to save time, Filament can automatically generate the [columns](#defining-importer-columns) for you, based on your model's database columns, using `--generate`:
```bash
php artisan make:filament-importer Product --generate
```
## Defining importer columns
To define the columns that can be imported, you need to override the `getColumns()` method on your importer class, returning an array of `ImportColumn` objects:
```php
use Filament\Actions\Imports\ImportColumn;
public static function getColumns(): array
{
return [
ImportColumn::make('name')
->requiredMapping()
->rules(['required', 'max:255']),
ImportColumn::make('sku')
->label('SKU')
->requiredMapping()
->rules(['required', 'max:32']),
ImportColumn::make('price')
->numeric()
->rules(['numeric', 'min:0']),
];
}
```
### Customizing the label of an import column
The label for each column will be generated automatically from its name, but you can override it by calling the `label()` method:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->label('SKU')
```
### Requiring an importer column to be mapped to a CSV column
You can call the `requiredMapping()` method to make a column required to be mapped to a column in the CSV. Columns that are required in the database should be required to be mapped:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->requiredMapping()
```
If you require a column in the database, you also need to make sure that it has a [`rules(['required'])` validation rule](#validating-csv-data).
### Validating CSV data
You can call the `rules()` method to add validation rules to a column. These rules will check the data in each row from the CSV before it is saved to the database:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->rules(['required', 'max:32'])
```
Any rows that do not pass validation will not be imported. Instead, they will be compiled into a new CSV of "failed rows", which the user can download after the import has finished. The user will be shown a list of validation errors for each row that failed.
### Casting state
Before [validation](#validating-csv-data), data from the CSV can be cast. This is useful for converting strings into the correct data type, otherwise validation may fail. For example, if you have a `price` column in your CSV, you may want to cast it to a float:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->castStateUsing(function (string $state): ?float {
if (blank($state)) {
return null;
}
$state = preg_replace('/[^0-9.]/', '', $state);
$state = floatval($state);
return round($state, precision: 2);
})
```
In this example, we pass in a function that is used to cast the `$state`. This function removes any non-numeric characters from the string, casts it to a float, and rounds it to two decimal places.
> Please note: if a column is not [required by validation](#validating-csv-data), and it is empty, it will not be cast.
Filament also ships with some built-in casting methods:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->numeric() // Casts the state to a float.
ImportColumn::make('price')
->numeric(decimalPlaces: 2) // Casts the state to a float, and rounds it to 2 decimal places.
ImportColumn::make('quantity')
->integer() // Casts the state to an integer.
ImportColumn::make('is_visible')
->boolean() // Casts the state to a boolean.
```
#### Mutating the state after it has been cast
If you're using a [built-in casting method](#casting-state) or [array cast](#handling-multiple-values-in-a-single-column-as-an-array), you can mutate the state after it has been cast by passing a function to the `castStateUsing()` method:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->numeric()
->castStateUsing(function (float $state): ?float {
if (blank($state)) {
return null;
}
return round($state * 100);
})
```
You can even access the original state before it was cast, by defining an `$originalState` argument in the function:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->numeric()
->castStateUsing(function (float $state, mixed $originalState): ?float {
// ...
})
```
### Importing relationships
You may use the `relationship()` method to import a relationship. At the moment, only `BelongsTo` relationships are supported. For example, if you have a `category` column in your CSV, you may want to import the category relationship:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship()
```
In this example, the `author` column in the CSV will be mapped to the `author_id` column in the database. The CSV should contain the primary keys of authors, usually `id`.
If the column has a value, but the author cannot be found, the import will fail validation. Filament automatically adds validation to all relationship columns, to ensure that the relationship is not empty when it is required.
#### Customizing the relationship import resolution
If you want to find a related record using a different column, you can pass the column name as `resolveUsing`:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: 'email')
```
You can pass in multiple columns to `resolveUsing`, and they will be used to find the author, in an "or" fashion. For example, if you pass in `['email', 'username']`, the record can be found by either their email or username:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: ['email', 'username'])
```
You can also customize the resolution process, by passing in a function to `resolveUsing`, which should return a record to associate with the relationship:
```php
use App\Models\Author;
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: function (string $state): ?Author {
return Author::query()
->where('email', $state)
->orWhere('username', $state)
->first();
})
```
You could even use this function to dynamically determine which columns to use to resolve the record:
```php
use App\Models\Author;
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('author')
->relationship(resolveUsing: function (string $state): ?Author {
if (filter_var($state, FILTER_VALIDATE_EMAIL)) {
return 'email';
}
return 'username';
})
```
### Handling multiple values in a single column as an array
You may use the `array()` method to cast the values in a column to an array. It accepts a delimiter as its first argument, which is used to split the values in the column into an array. For example, if you have a `documentation_urls` column in your CSV, you may want to cast it to an array of URLs:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('documentation_urls')
->array(',')
```
In this example, we pass in a comma as the delimiter, so the values in the column will be split by commas, and cast to an array.
#### Casting each item in an array
If you want to cast each item in the array to a different data type, you can chain the [built-in casting methods](#casting-state):
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('customer_ratings')
->array(',')
->integer() // Casts each item in the array to an integer.
```
#### Validating each item in an array
If you want to validate each item in the array, you can chain the `nestedRecursiveRules()` method:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('customer_ratings')
->array(',')
->integer()
->rules(['array'])
->nestedRecursiveRules(['integer', 'min:1', 'max:5'])
```
### Customizing how a column is filled into a record
If you want to customize how column state is filled into a record, you can pass a function to the `fillRecordUsing()` method:
```php
use App\Models\Product;
ImportColumn::make('sku')
->fillRecordUsing(function (Product $record, string $state): void {
$record->sku = strtoupper($state);
})
```
### Adding helper text below the import column
Sometimes, you may wish to provide extra information for the user before validation. You can do this by adding `helperText()` to a column, which gets displayed below the mapping select:
```php
use Filament\Forms\Components\TextInput;
ImportColumn::make('skus')
->array(',')
->helperText('A comma-separated list of SKUs.')
```
## Updating existing records when importing
When generating an importer class, you will see this `resolveRecord()` method:
```php
use App\Models\Product;
public function resolveRecord(): ?Product
{
// return Product::firstOrNew([
// // Update existing records, matching them by `$this->data['column_name']`
// 'email' => $this->data['email'],
// ]);
return new Product();
}
```
This method is called for each row in the CSV, and is responsible for returning a model instance that will be filled with the data from the CSV, and saved to the database. By default, it will create a new record for each row. However, you can customize this behavior to update existing records instead. For example, you might want to update a product if it already exists, and create a new one if it doesn't. To do this, you can uncomment the `firstOrNew()` line, and pass the column name that you want to match on. For a product, we might want to match on the `sku` column:
```php
use App\Models\Product;
public function resolveRecord(): ?Product
{
return Product::firstOrNew([
'sku' => $this->data['sku'],
]);
}
```
### Updating existing records when importing only
If you want to write an importer that only updates existing records, and does not create new ones, you can return `null` if no record is found:
```php
use App\Models\Product;
public function resolveRecord(): ?Product
{
return Product::query()
->where('sku', $this->data['sku'])
->first();
}
```
If you'd like to fail the import row if no record is found, you can throw a `RowImportFailedException` with a message:
```php
use App\Models\Product;
use Filament\Actions\Imports\Exceptions\RowImportFailedException;
public function resolveRecord(): ?Product
{
$product = Product::query()
->where('sku', $this->data['sku'])
->first();
if (! $product) {
throw new RowImportFailedException("No product found with SKU [{$this->data['sku']}].");
}
return $product;
}
```
When the import is completed, the user will be able to download a CSV of failed rows, which will contain the error messages.
### Ignoring blank state for an import column
By default, if a column in the CSV is blank, and mapped by the user, and it's not required by validation, the column will be imported as `null` in the database. If you'd like to ignore blank state, and use the existing value in the database instead, you can call the `ignoreBlankState()` method:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->ignoreBlankState()
```
## Using import options
The import action can render extra form components that the user can interact with when importing a CSV. This can be useful to allow the user to customize the behavior of the importer. For instance, you might want a user to be able to choose whether to update existing records when importing, or only create new ones. To do this, you can return options form components from the `getOptionsFormComponents()` method on your importer class:
```php
use Filament\Forms\Components\Checkbox;
public static function getOptionsFormComponents(): array
{
return [
Checkbox::make('updateExisting')
->label('Update existing records'),
];
}
```
Alternatively, you can pass a set of static options to the importer through the `options()` method on the action:
```php
use Filament\Actions\ImportAction;
ImportAction::make()
->importer(ProductImporter::class)
->options([
'updateExisting' => true,
])
```
Now, you can access the data from these options inside the importer class, by calling `$this->options`. For example, you might want to use it inside `resolveRecord()` to [update an existing product](#updating-existing-records-when-importing):
```php
use App\Models\Product;
public function resolveRecord(): ?Product
{
if ($this->options['updateExisting'] ?? false) {
return Product::firstOrNew([
'sku' => $this->data['sku'],
]);
}
return new Product();
}
```
## Improving import column mapping guesses
By default, Filament will attempt to "guess" which columns in the CSV match which columns in the database, to save the user time. It does this by attempting to find different combinations of the column name, with spaces, `-`, `_`, all cases insensitively. However, if you'd like to improve the guesses, you can call the `guess()` method with more examples of the column name that could be present in the CSV:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->guess(['id', 'number', 'stock-keeping unit'])
```
## Providing example CSV data
Before the user uploads a CSV, they have an option to download an example CSV file, containing all the available columns that can be imported. This is useful, as it allows the user to import this file directly into their spreadsheet software, and fill it out.
You can also add an example row to the CSV, to show the user what the data should look like. To fill in this example row, you can pass in an example column value to the `example()` method:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->example('ABC123')
```
By default, the name of the column is used in the header of the example CSV. You can customize the header per-column using `exampleHeader()`:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->exampleHeader('SKU')
```
## Using a custom user model
By default, the `imports` table has a `user_id` column. That column is constrained to the `users` table:
```php
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
```
In the `Import` 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:
```php
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()`:
```php
$table->foreignId('user_id')->constrained('admins')->cascadeOnDelete();
```
### Using a polymorphic user relationship
If you want to associate imports 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 `imports` table:
```php
$table->morphs('user');
```
Then, in a service provider's `boot()` method, you should call `Import::polymorphicUserRelationship()` to swap the `user()` relationship on the `Import` model to a `MorphTo` relationship:
```php
use Filament\Actions\Imports\Models\Import;
Import::polymorphicUserRelationship();
```
## Limiting the maximum number of rows that can be imported
To prevent server overload, you may wish to limit the maximum number of rows that can be imported from one CSV file. You can do this by calling the `maxRows()` method on the action:
```php
ImportAction::make()
->importer(ProductImporter::class)
->maxRows(100000)
```
## Changing the import chunk size
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:
```php
ImportAction::make()
->importer(ProductImporter::class)
->chunkSize(250)
```
If you are encountering memory or timeout issues when importing large CSV files, you may wish to reduce the chunk size.
## Changing the CSV delimiter
The default delimiter for CSVs is the comma (`,`). If your import uses a different delimiter, you may call the `csvDelimiter()` method on the action, passing a new one:
```php
ImportAction::make()
->importer(ProductImporter::class)
->csvDelimiter(';')
```
You can only specify a single character, otherwise an exception will be thrown.
## Changing the column header offset
If your column headers are not on the first row of the CSV, you can call the `headerOffset()` method on the action, passing the number of rows to skip:
```php
ImportAction::make()
->importer(ProductImporter::class)
->headerOffset(5)
```
## Customizing the import job
The default job for processing imports is `Filament\Actions\Imports\Jobs\ImportCsv`. If you want to extend this class and override any of its methods, you may replace the original class in the `register()` method of a service provider:
```php
use App\Jobs\ImportCsv;
use Filament\Actions\Imports\Jobs\ImportCsv as BaseImportCsv;
$this->app->bind(BaseImportCsv::class, ImportCsv::class);
```
Or, you can pass the new job class to the `job()` method on the action, to customize the job for a specific import:
```php
use App\Jobs\ImportCsv;
ImportAction::make()
->importer(ProductImporter::class)
->job(ImportCsv::class)
```
### Customizing the import queue and connection
By default, the import system will use the default queue and connection. If you'd like to customize the queue used for jobs of a certain importer, you may override the `getJobQueue()` method in your importer class:
```php
public function getJobQueue(): ?string
{
return 'imports';
}
```
You can also customize the connection used for jobs of a certain importer, by overriding the `getJobConnection()` method in your importer class:
```php
public function getJobConnection(): ?string
{
return 'sqs';
}
```
### Customizing the import job middleware
By default, the import system will only process one job at a time from each import. This is to prevent the server from being overloaded, and other jobs from being delayed by large imports. That functionality is defined in the `WithoutOverlapping` middleware on the importer class:
```php
public function getJobMiddleware(): array
{
return [
(new WithoutOverlapping("import{$this->import->getKey()}"))->expireAfter(600),
];
}
```
If you'd like to customize the middleware that is applied to jobs of a certain importer, you may override this method in your importer class. You can read more about job middleware in the [Laravel docs](https://laravel.com/docs/queues#job-middleware).
### Customizing the import job retries
By default, the import system will retry a job for 24 hours. This is to allow for temporary issues, such as the database being unavailable, to be resolved. That functionality is defined in the `getJobRetryUntil()` method on the importer class:
```php
use Carbon\CarbonInterface;
public function getJobRetryUntil(): ?CarbonInterface
{
return now()->addDay();
}
```
If you'd like to customize the retry time for jobs of a certain importer, you may override this method in your importer class. You can read more about job retries in the [Laravel docs](https://laravel.com/docs/queues#time-based-attempts).
### Customizing the import job tags
By default, the import system will tag each job with the ID of the import. This is to allow you to easily find all jobs related to a certain import. That functionality is defined in the `getJobTags()` method on the importer class:
```php
public function getJobTags(): array
{
return ["import{$this->import->getKey()}"];
}
```
If you'd like to customize the tags that are applied to jobs of a certain importer, you may override this method in your importer class.
### Customizing the import job batch name
By default, the import system doesn't define any name for the job batches. If you'd like to customize the name that is applied to job batches of a certain importer, you may override the `getJobBatchName()` method in your importer class:
```php
public function getJobBatchName(): ?string
{
return 'product-import';
}
```
## Customizing import validation messages
The import system will automatically validate the CSV file before it is imported. If there are any errors, the user will be shown a list of them, and the import will not be processed. If you'd like to override any default validation messages, you may do so by overriding the `getValidationMessages()` method on your importer class:
```php
public function getValidationMessages(): array
{
return [
'name.required' => 'The name column must not be empty.',
];
}
```
To learn more about customizing validation messages, read the [Laravel docs](https://laravel.com/docs/validation#customizing-the-error-messages).
### Customizing import validation attributes
When columns fail validation, their label is used in the error message. To customize the label used in field error messages, use the `validationAttribute()` method:
```php
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('name')
->validationAttribute('full name')
```
## Customizing import file validation
You can add new [Laravel validation rules](https://laravel.com/docs/validation#available-validation-rules) for the import file using the `fileRules()` method:
```php
use Illuminate\Validation\Rules\File;
ImportAction::make()
->importer(ProductImporter::class)
->fileRules([
'max:1024',
// or
File::types(['csv', 'txt'])->max(1024),
]),
```
## Lifecycle hooks
Hooks may be used to execute code at various points within an importer's lifecycle, like before a record is saved. To set up a hook, create a protected method on the importer class with the name of the hook:
```php
protected function beforeSave(): void
{
// ...
}
```
In this example, the code in the `beforeSave()` method will be called before the validated data from the CSV is saved to the database.
There are several available hooks for importers:
```php
use Filament\Actions\Imports\Importer;
class ProductImporter extends Importer
{
// ...
protected function beforeValidate(): void
{
// Runs before the CSV data for a row is validated.
}
protected function afterValidate(): void
{
// Runs after the CSV data for a row is validated.
}
protected function beforeFill(): void
{
// Runs before the validated CSV data for a row is filled into a model instance.
}
protected function afterFill(): void
{
// Runs after the validated CSV data for a row is filled into a model instance.
}
protected function beforeSave(): void
{
// Runs before a record is saved to the database.
}
protected function beforeCreate(): void
{
// Similar to `beforeSave()`, but only runs when creating a new record.
}
protected function beforeUpdate(): void
{
// Similar to `beforeSave()`, but only runs when updating an existing record.
}
protected function afterSave(): void
{
// Runs after a record is saved to the database.
}
protected function afterCreate(): void
{
// Similar to `afterSave()`, but only runs when creating a new record.
}
protected function afterUpdate(): void
{
// Similar to `afterSave()`, but only runs when updating an existing record.
}
}
```
Inside these hooks, you can access the current row's data using `$this->data`. You can also access the original row of data from the CSV, before it was [cast](#casting-state) or mapped, using `$this->originalData`.
The current record (if it exists yet) is accessible in `$this->record`, and the [import form options](#using-import-options) using `$this->options`.
## Authorization
By default, only the user who started the import may access the failure CSV file that gets generated if part of an import fails. If you'd like to customize the authorization logic, you may create an `ImportPolicy` class, and [register it in your `AuthServiceProvider`](https://laravel.com/docs/10.x/authorization#registering-policies):
```php
use App\Policies\ImportPolicy;
use Filament\Actions\Imports\Models\Import;
protected $policies = [
Import::class => ImportPolicy::class,
];
```
The `view()` method of the policy will be used to authorize access to the failure CSV file.
Please note that if you define a policy, the existing logic of ensuring only the user who started the import can access the failure CSV file will be removed. You will need to add that logic to your policy if you want to keep it:
```php
use App\Models\User;
use Filament\Actions\Imports\Models\Import;
public function view(User $user, Import $import): bool
{
return $import->user()->is($user);
}
```

View File

@@ -0,0 +1,689 @@
---
title: Export action
---
## Overview
Filament v3.2 introduced a prebuilt 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](https://laravel.com/docs/queues#job-batching) and [database notifications](../../notifications/database-notifications#overview), 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:
```bash
# Laravel 11 and higher
php artisan make:queue-batches-table
php artisan make:notifications-table
# Laravel 10
php artisan queue:batches-table
php artisan notifications:table
php artisan vendor:publish --tag=filament-actions-migrations
php artisan migrate
```
> If you're using PostgreSQL, make sure that the `data` column in the notifications migration is using `json()`: `$table->json('data')`.
> If you're using UUIDs for your `User` model, make sure that your `notifiable` column in the notifications migration is using `uuidMorphs()`: `$table->uuidMorphs('notifiable')`.
You may use the `ExportAction` like so:
```php
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 instead, you can use `Filament\Tables\Actions\ExportAction`:
```php
use App\Filament\Exports\ProductExporter;
use Filament\Tables\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\Tables\Actions\ExportBulkAction`:
```php
use App\Filament\Exports\ProductExporter;
use Filament\Tables\Actions\ExportBulkAction;
use Filament\Tables\Table;
public function table(Table $table): Table
{
return $table
->bulkActions([
ExportBulkAction::make()
->exporter(ProductExporter::class)
]);
}
```
The ["exporter" class needs to be created](#creating-an-exporter) to tell Filament how to export each row.
## Creating an exporter
To create an exporter class for a model, you may use the `make:filament-exporter` command, passing the name of a model:
```bash
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](#defining-exporter-columns) that can be exported.
### Automatically generating exporter columns
If you'd like to save time, Filament can automatically generate the [columns](#defining-exporter-columns) for you, based on your model's database columns, using `--generate`:
```bash
php artisan make:filament-exporter Product --generate
```
## Defining exporter columns
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:
```php
use Filament\Actions\Exports\ExportColumn;
public static function getColumns(): array
{
return [
ExportColumn::make('name'),
ExportColumn::make('sku')
->label('SKU'),
ExportColumn::make('price'),
];
}
```
### Customizing the label of an export column
The label for each column will be generated automatically from its name, but you can override it by calling the `label()` method:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('sku')
->label('SKU')
```
### Configuring the default column selection
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:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->enabledByDefault(false)
```
### Disabling column selection
By default, user will be asked which columns they would like to export. You can disable this functionality using `columnMapping(false)`:
```php
use App\Filament\Exports\ProductExporter;
ExportAction::make()
->exporter(ProductExporter::class)
->columnMapping(false)
```
### Calculated export column state
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`:
```php
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);
})
```
### Formatting the value of an export column
You may instead pass a custom formatting callback to `formatStateUsing()`, which accepts the `$state` of the cell, and optionally the Eloquent `$record`:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('status')
->formatStateUsing(fn (string $state): string => __("statuses.{$state}"))
```
If there are [multiple values](#exporting-multiple-values-in-a-cell) in the column, the function will be called for each value.
#### Limiting text length
You may `limit()` the length of the cell's value:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->limit(50)
```
#### Limiting word count
You may limit the number of `words()` displayed in the cell:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->words(10)
```
#### Adding a prefix or suffix
You may add a `prefix()` or `suffix()` to the cell's value:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('domain')
->prefix('https://')
->suffix('.com')
```
### Exporting multiple values in a cell
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:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('tags')
->listAsJson()
```
### Displaying data from relationships
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:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('author.name')
```
### Counting relationships
If you wish to count the number of related records in a column, you may use the `counts()` method:
```php
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](https://laravel.com/docs/eloquent-relationships#counting-related-models) for storing the result.
If you'd like to scope the relationship before calculating, 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:
```php
use Filament\Actions\Exports\ExportColumn;
use Illuminate\Database\Eloquent\Builder;
ExportColumn::make('users_count')->counts([
'users' => fn (Builder $query) => $query->where('is_active', true),
])
```
### Determining relationship existence
If you simply wish to indicate whether related records exist in a column, you may use the `exists()` method:
```php
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](https://laravel.com/docs/eloquent-relationships#other-aggregate-functions) for storing the result.
If you'd like to scope the relationship before calculating, 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:
```php
use Filament\Actions\Exports\ExportColumn;
use Illuminate\Database\Eloquent\Builder;
ExportColumn::make('users_exists')->exists([
'users' => fn (Builder $query) => $query->where('is_active', true),
])
```
### Aggregating relationships
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:
```php
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](https://laravel.com/docs/eloquent-relationships#other-aggregate-functions) for storing the result.
If you'd like to scope the relationship before calculating, 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:
```php
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')
```
## Configuring the export formats
By default, the export action will allow the user to choose between both CSV and XLSX formats. You can use the `ExportFormat` enum to customize this, by passing an array of formats to the `formats()` method on the action:
```php
use App\Filament\Exports\ProductExporter;
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:
```php
use Filament\Actions\Exports\Enums\ExportFormat;
public function getFormats(): array
{
return [
ExportFormat::Csv,
];
}
```
## Modifying the export query
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:
```php
use App\Filament\Exports\ProductExporter;
use Illuminate\Database\Eloquent\Builder;
ExportAction::make()
->exporter(ProductExporter::class)
->modifyQueryUsing(fn (Builder $query) => $query->where('is_active', true))
```
Alternatively, you can override the `modifyQuery()` method on the exporter class, which will modify the query for all actions that use that exporter:
```php
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'],
]),
]);
}
```
## Configuring the export filesystem
### Customizing the storage disk
By default, exported files will be uploaded to the storage disk defined in the [configuration file](../installation#publishing-configuration). You can also set the `FILAMENT_FILESYSTEM_DISK` environment variable to change this.
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:
```php
ExportAction::make()
->exporter(ProductExporter::class)
->fileDisk('s3')
```
Alternatively, you can override the `getFileDisk()` method on the exporter class, returning the name of the disk:
```php
public function getFileDisk(): string
{
return 's3';
}
```
### Configuring the export file names
By default, exported files will have a name generated based on the ID and type of the export. You can also use the `fileName()` method on the action to customize the file name:
```php
use Filament\Actions\Exports\Models\Export;
ExportAction::make()
->exporter(ProductExporter::class)
->fileName(fn (Export $export): string => "products-{$export->getKey()}.csv")
```
Alternatively, you can override the `getFileName()` method on the exporter class, returning a string:
```php
use Filament\Actions\Exports\Models\Export;
public function getFileName(Export $export): string
{
return "products-{$export->getKey()}.csv";
}
```
## Using export options
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:
```php
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:
```php
ExportAction::make()
->exporter(ProductExporter::class)
->options([
'descriptionLimit' => 250,
])
```
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](#formatting-the-value-of-an-export-column):
```php
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()`:
```php
use Filament\Actions\Exports\ExportColumn;
ExportColumn::make('description')
->limit(fn (array $options): int => $options['descriptionLimit'] ?? 100)
```
## Using a custom user model
By default, the `exports` table has a `user_id` column. That column is constrained to the `users` table:
```php
$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:
```php
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()`:
```php
$table->foreignId('user_id')->constrained('admins')->cascadeOnDelete();
```
### Using a polymorphic user relationship
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:
```php
$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:
```php
use Filament\Actions\Exports\Models\Export;
Export::polymorphicUserRelationship();
```
## Limiting the maximum number of rows that can be exported
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:
```php
ExportAction::make()
->exporter(ProductExporter::class)
->maxRows(100000)
```
## Changing the export chunk size
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:
```php
ExportAction::make()
->exporter(ProductExporter::class)
->chunkSize(250)
```
If you are encountering memory or timeout issues when exporting large CSV files, you may wish to reduce the chunk size.
## Changing the CSV delimiter
The default delimiter for CSVs is the comma (`,`). If you want to export using a different delimiter, you may override the `getCsvDelimiter()` method on the exporter class, returning a new one:
```php
public static function getCsvDelimiter(): string
{
return ';';
}
```
You can only specify a single character, otherwise an exception will be thrown.
## Styling XLSX cells
If you want to style the cells of the XLSX file, you may override the `getXlsxCellStyle()` method on the exporter class, returning an [OpenSpout `Style` object](https://github.com/openspout/openspout/blob/4.x/docs/documentation.md#styling):
```php
use OpenSpout\Common\Entity\Style\Style;
public function getXlsxCellStyle(): ?Style
{
return (new Style())
->setFontSize(12)
->setFontName('Consolas');
}
```
If you want to use a different style for the header cells of the XLSX file only, you may override the `getXlsxHeaderCellStyle()` method on the exporter class, returning an [OpenSpout `Style` object](https://github.com/openspout/openspout/blob/4.x/docs/documentation.md#styling):
```php
use OpenSpout\Common\Entity\Style\CellAlignment;
use OpenSpout\Common\Entity\Style\CellVerticalAlignment;
use OpenSpout\Common\Entity\Style\Color;
use OpenSpout\Common\Entity\Style\Style;
public function getXlsxHeaderCellStyle(): ?Style
{
return (new Style())
->setFontBold()
->setFontItalic()
->setFontSize(14)
->setFontName('Consolas')
->setFontColor(Color::rgb(255, 255, 77))
->setBackgroundColor(Color::rgb(0, 0, 0))
->setCellAlignment(CellAlignment::CENTER)
->setCellVerticalAlignment(CellVerticalAlignment::CENTER);
}
```
## Customizing the export job
The default job for processing exports is `Filament\Actions\Exports\Jobs\PrepareCsvExport`. If you want to extend this class and override any of its methods, you may replace the original class in the `register()` method of a service provider:
```php
use App\Jobs\PrepareCsvExport;
use Filament\Actions\Exports\Jobs\PrepareCsvExport as BasePrepareCsvExport;
$this->app->bind(BasePrepareCsvExport::class, PrepareCsvExport::class);
```
Or, you can pass the new job class to the `job()` method on the action, to customize the job for a specific export:
```php
use App\Jobs\PrepareCsvExport;
ExportAction::make()
->exporter(ProductExporter::class)
->job(PrepareCsvExport::class)
```
### Customizing the export queue and connection
By default, the export system will use the default queue and connection. If you'd like to customize the queue used for jobs of a certain exporter, you may override the `getJobQueue()` method in your exporter class:
```php
public function getJobQueue(): ?string
{
return 'exports';
}
```
You can also customize the connection used for jobs of a certain exporter, by overriding the `getJobConnection()` method in your exporter class:
```php
public function getJobConnection(): ?string
{
return 'sqs';
}
```
### Customizing the export job middleware
By default, the export system will only process one job at a time from each export. This is to prevent the server from being overloaded, and other jobs from being delayed by large exports. That functionality is defined in the `WithoutOverlapping` middleware on the exporter class:
```php
public function getJobMiddleware(): array
{
return [
(new WithoutOverlapping("export{$this->export->getKey()}"))->expireAfter(600),
];
}
```
If you'd like to customize the middleware that is applied to jobs of a certain exporter, you may override this method in your exporter class. You can read more about job middleware in the [Laravel docs](https://laravel.com/docs/queues#job-middleware).
### Customizing the export job retries
By default, the export system will retry a job for 24 hours. This is to allow for temporary issues, such as the database being unavailable, to be resolved. That functionality is defined in the `getJobRetryUntil()` method on the exporter class:
```php
use Carbon\CarbonInterface;
public function getJobRetryUntil(): ?CarbonInterface
{
return now()->addDay();
}
```
If you'd like to customize the retry time for jobs of a certain exporter, you may override this method in your exporter class. You can read more about job retries in the [Laravel docs](https://laravel.com/docs/queues#time-based-attempts).
### Customizing the export job tags
By default, the export system will tag each job with the ID of the export. This is to allow you to easily find all jobs related to a certain export. That functionality is defined in the `getJobTags()` method on the exporter class:
```php
public function getJobTags(): array
{
return ["export{$this->export->getKey()}"];
}
```
If you'd like to customize the tags that are applied to jobs of a certain exporter, you may override this method in your exporter class.
### Customizing the export job batch name
By default, the export system doesn't define any name for the job batches. If you'd like to customize the name that is applied to job batches of a certain exporter, you may override the `getJobBatchName()` method in your exporter class:
```php
public function getJobBatchName(): ?string
{
return 'product-export';
}
```
## Authorization
By default, only the user who started the export may download files that get generated. If you'd like to customize the authorization logic, you may create an `ExportPolicy` class, and [register it in your `AuthServiceProvider`](https://laravel.com/docs/10.x/authorization#registering-policies):
```php
use App\Policies\ExportPolicy;
use Filament\Actions\Exports\Models\Export;
protected $policies = [
Export::class => ExportPolicy::class,
];
```
The `view()` method of the policy will be used to authorize access to the downloads.
Please note that if you define a policy, the existing logic of ensuring only the user who started the export can access it will be removed. You will need to add that logic to your policy if you want to keep it:
```php
use App\Models\User;
use Filament\Actions\Exports\Models\Export;
public function view(User $user, Export $export): bool
{
return $export->user()->is($user);
}
```