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 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:If you’re using PostgreSQL, make sure that thedatacolumn in the notifications migration is usingjson():$table->json('data').
If you’re using UUIDs for yourYou may use theUsermodel, make sure that yournotifiablecolumn in the notifications migration is usinguuidMorphs():$table->uuidMorphs('notifiable').
ImportAction like so:
Filament\Tables\Actions\ImportAction:
ImportAction in the same place, you should give each a unique name in the make() method:
Creating an importer
To create an importer class for a model, you may use themake:filament-importer command, passing the name of a model:
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:
Defining importer columns
To define the columns that can be imported, you need to override thegetColumns() method on your importer class, returning an array of ImportColumn objects:
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 thelabel() method:
Requiring an importer column to be mapped to a CSV column
You can call therequiredMapping() 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:
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():
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 therules() 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:
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 aprice column in your CSV, you may want to cast it to a float:
$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, and it is empty, it will not be cast.Filament also ships with some built-in casting methods:
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 thecastStateUsing() method:
$originalState argument in the function:
Importing relationships
You may use therelationship() 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:
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 asresolveUsing:
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:
resolveUsing, which should return a record to associate with the relationship:
Handling multiple values in a single column as an array
You may use thearray() 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:
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:Validating each item in an array
If you want to validate each item in the array, you can chain thenestedRecursiveRules() method:
Marking column data as sensitive
When import rows fail validation, they are logged to the database, ready for export when the import completes. You may want to exclude certain columns from this logging to avoid storing sensitive data in plain text. To achieve this, you can use thesensitive() method on the ImportColumn to prevent its data from being logged:
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 thefillRecordUsing() method:
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 addinghelperText() to a column, which gets displayed below the mapping select:
Updating existing records when importing
When generating an importer class, you will see thisresolveRecord() method:
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:
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 returnnull if no record is found:
RowImportFailedException with a message:
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 asnull 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:
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 thegetOptionsFormComponents() method on your importer class:
options() method on the action:
$this->options. For example, you might want to use it inside resolveRecord() to update an existing 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:
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 theexample() method:
examples() method:
exampleHeader():
Using a custom user model
By default, theimports table has a user_id column. That column is constrained to the users table:
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:
users, you should pass that table name to constrained():
Using a polymorphic user relationship
If you want to associate imports with multiple user models, you can use a polymorphicMorphTo relationship instead. To do this, you need to replace the user_id column in the imports table:
boot() method, you should call Import::polymorphicUserRelationship() to swap the user() relationship on the Import model to a MorphTo relationship:
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 themaxRows() method on the action:
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 thechunkSize() method on the action:
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:
Changing the column header offset
If your column headers are not on the first row of the CSV, you can call theheaderOffset() method on the action, passing the number of rows to skip:
Customizing the import job
The default job for processing imports isFilament\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:
job() method on the action, to customize the job for a specific import:
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 thegetJobQueue() method in your importer class:
getJobConnection() method in your importer class:
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 theWithoutOverlapping middleware on the importer class:
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 thegetJobRetryUntil() method on the importer class:
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 thegetJobTags() method on the 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 thegetJobBatchName() method in your importer class:
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 thegetValidationMessages() method on your importer class:
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 thevalidationAttribute() method:
Customizing import file validation
You can add new Laravel validation rules for the import file using thefileRules() method:
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:beforeSave() method will be called before the validated data from the CSV is saved to the database.
There are several available hooks for importers:
$this->data. You can also access the original row of data from the CSV, before it was cast or mapped, using $this->originalData.
The current record (if it exists yet) is accessible in $this->record, and the import form 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 anImportPolicy class, and register it in your AuthServiceProvider:
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: