Import action
Introduction
Filament includes an 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 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 imports:
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 import notifications in a panel, you can enable them in the panel configuration.
NOTE
If you're using PostgreSQL, make sure that the data column in the notifications migration is using json(): $table->json('data').
NOTE
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:
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, you may do so like this:
use App\Filament\Imports\ProductImporter;
use Filament\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 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:
use Filament\Actions\ImportAction;
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:
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 that can be imported.
Automatically generating importer columns
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-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:
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:
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:
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.
If a column is not mapped, it will not be validated since there is no data to validate.
If you allow an import to create records as well as update existing ones, but only require a column to be mapped when creating records as it's a required field, you can use the requiredMappingForNewRecordsOnly() method instead of requiredMapping():
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('sku')
->requiredMappingForNewRecordsOnly()
If the resolveRecord() method returns a model instance that is not saved in the database yet, the column will be required to be mapped, just for that row. If the user does not map the column, and one of the rows in the import does not yet exist in the database, just that row will fail and a message will be added to the failed rows CSV after every row has been analyzed.
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:
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.
As well as allowing a static value, the rules() method also accepts a function to dynamically calculate it. You can inject various utilities into the function as parameters.
| Utility | Type | Parameter | Description |
|---|---|---|---|
| Import column | Filament\Actions\Imports\ImportColumn | $column | The current import column instance. |
| Data | array<string, mixed> | $data | The processed data for the record that is currently being imported. |
| Importer | ?Filament\Actions\Imports\Importer | $importer | The instance of the importer class that is currently being used for importing data. |
| Options | array<string, mixed> | $options | The options that were defined when the import started. |
| Original data | array<string, mixed> | $originalData | The original data for the record that is currently being imported, before it was processed. |
| Eloquent record | ?Illuminate\Database\Eloquent\Model | $record | The Eloquent record that is currently being imported. |
Casting state
Before validation, 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:
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);
})
As well as $state, the castStateUsing() method allows you to inject various utilities into the function as parameters.
| Utility | Type | Parameter | Description |
|---|---|---|---|
| Import column | Filament\Actions\Imports\ImportColumn | $column | The current import column instance. |
| Data | array<string, mixed> | $data | The processed data for the record that is currently being imported. |
| Importer | ?Filament\Actions\Imports\Importer | $importer | The instance of the importer class that is currently being used for importing data. |
| Options | array<string, mixed> | $options | The options that were defined when the import started. |
| Original data | array<string, mixed> | $originalData | The original data for the record that is currently being imported, before it was processed. |
| Original state | mixed | $originalState | The state to cast, before it was processed by other casting methods. |
| Eloquent record | ?Illuminate\Database\Eloquent\Model | $record | The Eloquent record that is currently being imported. |
| State | mixed | $state | The state to cast, after it has been processed by other casting methods. |
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.
NOTE
If a column is not required by validation, and it is empty, it will not be cast.
Filament also ships with some built-in casting methods:
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 or array cast, you can mutate the state after it has been cast by passing a function to the castStateUsing() method:
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:
use Filament\Actions\Imports\ImportColumn;
ImportColumn::make('price')
->numeric()
->castStateUsing(function (float $state, mixed $originalState): ?float {
// ...
})
As well as $state, the castStateUsing() method allows you to inject various utilities into the function as parameters.
| Utility | Type | Parameter | Description |
|---|---|---|---|
| Import column | Filament\Actions\Imports\ImportColumn | $column | The current import column instance. |
| Data | array<string, mixed> | $data | The processed data for the record that is currently being imported. |
| Importer | ?Filament\Actions\Imports\Importer | $importer | The instance of the importer class that is currently being used for importing data. |
| Options | array<string, mixed> | $options | The options that were defined when the import started. |
| Original data | array<string, mixed> | $originalData | The original data for the record that is currently being imported, before it was processed. |
| Original state | mixed | $originalState | The state to cast, before it was processed by other casting methods. |
| Eloquent record | ?Illuminate\Database\Eloquent\Model | $record | The Eloquent record that is currently being imported. |
| State | mixed | $state | The state to cast, after it has been processed by other casting methods. |
