konnco/filament-import
Konnco Filament Import adds CSV and Excel import actions to Filament resources. Define fields with validation and uniqueness rules, map spreadsheet columns to model attributes, and import rows into Eloquent records from a simple, configurable action.
Install
composer require konnco/filament-import
There is no panel plugin to register — the package ships Filament actions, so it plugs directly into an existing Resource. Publish config/assets only if you need to change the upload disk or sample-file location:
php artisan vendor:publish --tag="filament-import-config"
Pin the version to your Filament major. As of 3.0.0 the package has jumped two majors from the old 1.6.1 line, and the majors track Filament/PHP compatibility rather than package features:
1.x — legacy Filament 2 era2.x — Filament 3 era3.0.0 — current line, adds Laravel 11 support and fixes local-disk uploadsInstall with an explicit constraint ("konnco/filament-import": "^3.0") rather than floating, and confirm your Filament major before upgrading — see Gotchas.
First use case: import users from CSV on a Resource list page
use Konnco\FilamentImport\Actions\ImportAction;
use Konnco\FilamentImport\Actions\ImportField;
// In your ListUsers page (or the Resource's table header actions)
protected function getHeaderActions(): array
{
return [
ImportAction::make()
->fields([
ImportField::make('name')->required(),
ImportField::make('email')->required()->unique(),
ImportField::make('password')
->mutateBeforeCreate(fn ($value) => Hash::make($value)),
])
->handleRecordCreation(function (array $data) {
return User::create($data);
}),
];
}
The user clicks Import, uploads a CSV/XLSX, maps the uploaded columns onto your fields, reviews a preview/validation step, and confirms. Rows are then fed to handleRecordCreation.
Where to look first
ImportAction (modal, mapping, and creation pipeline) and ImportField (per-column rules and mutation hooks) — these two classes are the whole public surface.Importer abstraction (if your version supports a dedicated importer class) — reach for it once the same field set is used in more than one place.disk key — since 3.0.0 local (non-S3) disks work again; verify which disk your uploads land on before debugging anything else.Inline fields vs. a dedicated importer class. Start inline (->fields([...])->handleRecordCreation(...)) for one-off imports. As soon as a second Resource or a second file format needs the same columns, move the field definitions and creation logic into a reusable importer class / service and point the action at it. This also makes the logic unit-testable without touching Filament's UI layer.
Mapping human-readable headers. Don't name fields after database columns; give them labels (ImportField::make('phone')->label('Mobile Number')). The mapping step becomes self-explanatory, and you can later rename the column without breaking saved imports.
Resolve foreign keys during mutation. Users will never have your category_id, they'll have a category name. Do the lookup in the field mutation and fail loudly if it can't be resolved:
ImportField::make('category_id')
->label('Category')
->mutateBeforeCreate(fn ($value) => Category::firstOrCreate(['name' => trim($value)])->id),
For multi-pass relationships (orders + order lines), run two separate imports in sequence rather than trying to nest.
Upsert / idempotent re-imports. The common real-world need is "re-upload the corrected spreadsheet." Mark the natural key ->unique() and handle the update branch explicitly (via handleRecordUpdate, or by doing updateOrCreate inside handleRecordCreation). Without this, the second upload creates duplicates and the user blames the feature.
Tenant and ownership scoping. Multi-tenant panels: never trust a tenant_id column from the spreadsheet. Inject it in the mutation from Filament::getTenant() (or auth()->id() for an owner column) so imports can't cross tenant boundaries.
Sanitize data types in mutations, not in the database. Excel gives you untyped strings: trim whitespace, strip currency symbols, parse dates with Carbon::parse, uppercase country codes, normalise phone numbers. Every one of these belongs in mutateBeforeCreate/a shared normalizance, otherwise the data quality problem shows up as a support ticket.
Guard the action. Treat import as a privileged operation: ->visible(fn () => auth()->user()->can('import users')) plus a policy check on the model. Import is a bulk-write bypass of the edit form and deserves its own permission.
Large files. The action is request-bound and synchronous. Set a hard row cap in the modal description, validate file size via the uploaded file rules, and for anything above a few thousand rows route users to a queued Laravel Excel job instead. Don't let the UI promise more than PHP's max_execution_time can deliver.
Testing. Extract the creation closure to a class and test it with plain arrays — you get 90% of the coverage without driving a Livewire file upload.
3.0.0 release bundles the Filament-major alignment plus Laravel 11 support; release notes are sparse, so treat any upgrade as "read the changelog and smoke-test imports." Pin in composer.json and re-verify after every Filament or Laravel upgrade — this is not a package to float on *.3.0.0 ships the fix ("import with local disks not working after last beta release"). If you're developing against the beta or copied beta-era config, set disk explicitly in the published config to your local/public disk and re-test; silently failing uploads/reads are the symptom.^3.0, which in turn implies a specific Filament major. Check the full constraint matrix before planning the upgrade.->unique(). The uniqueness check usually runs per row against the database, so two identical emails in the same spreadsheet can both pass the preview. Dedupe the parsed collection yourself, or use updateOrCreate so the second row simply overwrites the first.created_at defaults may not fire. Check whether your business logic lives in observers before enabling it.8123456 or 1.23E+15. Store these as string columns, format the template column as text, and cast explicitly in the mutation.44927). Detect numeric date fields and convert inside the mutation rather than expecting a date string.DB::transaction for small files, or accept partial imports and surface a clear "N imported, M failed" summary to the user.upload_max_filesize and post_max_size failures manifest as a silent "no file" validation error. Compare the two values in php.ini before debugging the action. With the new local-disk fix, a missing storage:link is the next thing to check.dd() in a mutation will kill the Livewire request — write to Log::info() with the row payload instead. Reproduce bugs by feeding the raw array into your creation handler in a tinker session or test, not by re-uploading through the browser.ImportField can be subclassed to package up a common pattern (e.g. a TenantScopedField), the action supports the usual Filament modifiers (modalHeading, modalDescription, icon, visible, authorize), and the language files are publishable if you need translated labels for non-English staff.How can I help you explore Laravel packages today?