cube-agency/filament-excel
Filament Excel adds Excel import and export to Filament admin panels, letting you download resource tables and import spreadsheet data with configurable columns, queues, and notifications. Simple setup for Laravel and Filament apps.
Install the package via Composer and let Filament's asset/plugin discovery pick it up:
composer require <vendor>/filament-excel
php artisan filament:assets # only needed if the package ships JS/CSS
First places to look, in order:
composer.json of the package — confirm the required PHP, laravel/framework, and filament/filament version constraints before you upgrade an existing app. Filament v2/v3/v4 compatibility is the single most common blocker; a package pinned to ^3.0 will not install alongside Filament v4.src/*ServiceProvider.php) — check whether it auto-registers a plugin (Filament::registerPlugin()), publishes a config file, or registers export/import actions on list and table pages.config/ — if a config file exists, publish it early (php artisan vendor:publish --tag=...) so you can see every knob rather than reading the source.src/Exports/ and src/Imports/ (or Actions/) — the public surface area. Note the base classes your resource classes are expected to extend or the traits to apply.First use case to validate end-to-end: add an export action to a single Filament resource (e.g. UserResource), export to .xlsx, and open the file. That exercises the whole path — Filament table query → query builder/chunking → spreadsheet writer → download response — in about ten minutes and surfaces 90% of misconfiguration.
// UsersTable.php (Filament resource table)
->headerActions([
\Filament\Tables\Actions\ExportAction::make()
->exporter(UserExporter::class),
])
// app/Filament/Exports/UserExporter.php
class UserExporter extends \Filament\Actions\Exports\Exporter
{
protected static ?string $model = User::class;
public static function getColumns(): array
{
return [
ExportColumn::make('id'),
ExportColumn::make('name'),
ExportColumn::make('roles.name')->label('Roles'),
ExportColumn::make('created_at'),
];
}
}
If the package ships its own action classes, swap the imports accordingly — the flow (action on the table → exporter class → getColumns()) is the consistent pattern across Filament Excel tooling.
Exporter-per-resource, not per-table. Define one exporter class per model and reuse it across resources, bulk actions, and scheduled jobs. Keep getColumns() declarative and free of request-dependent logic; if a column needs context, prefer Filament's ExportColumn::make()->formatStateUsing() or a modifyQuery() hook over branching inside getColumns().
Bulk vs. all-rows exports are different code paths. ExportAction::make() (header action) exports everything matching the table filters; ExportBulkAction::make() exports only checked rows. Both should be wired — users expect the header button to respect active filters, and they will report "the export is wrong" when it doesn't. Verify filter propagation explicitly with a filtered table.
Chunking for large datasets. Most Filament Excel implementations stream via WithChunkReading / query()->chunk(). For anything above ~10k rows, set an explicit chunk size (e.g. 500–1000) and confirm the export runs through a queued job rather than a synchronous request. Long synchronous exports on PHP-FPM will hit max_execution_time and produce a 504 with no file.
Queued exports for the day-to-day path. Configure queue_connection in the package config and set up the notification channel (database notifications is the pragmatic default — Filament's notification bell then shows "Export complete"). This turns exports from "block the browser for 90 seconds" into fire-and-forget, and it is the pattern production users actually feel.
Imports: mirror the export shape. Use a matching importer with getColumns(), resolveRecordUsing() for upserts, and a UserImport model/ImportFailedRow handling. Always define an explicit unique key or resolveRecordUsing() — without it, every row is an insert and re-running the same sheet duplicates data.
Wrap writes in a transaction per chunk. For imports, DB::transaction() around each chunk gives you partial-success semantics without corrupting a sheet. Let the package record failures if it supports it; otherwise collect exceptions into a failed-rows array and report back.
Testing pattern. In feature tests, call the exporter directly (Exporter::make()->export(...)) or hit the Filament action via Livewire's callTableAction('export', ...) and assert the response downloads a file of the expected MIME type. Assert on row count and a couple of cell values, not on the full binary — spreadsheet binaries differ across library versions and will make tests flaky.
Integration with existing tooling. If you already run maatwebsite/excel for other parts of the app, check which underlying writer the package depends on; duplicating two spreadsheet libraries in composer.lock is common and mostly harmless, but conflicting PHPExcel/PhpSpreadsheet version constraints are not.
composer require failing with a dependency conflict on filament/filament is the #1 install issue. Read the constraint in the package's composer.json rather than trusting the README's badge. Forking or --with-dependencies is rarely the answer; pin Filament instead.ExportColumn::make('roles.name') works for relationships in newer Filament versions, but dot-notation can silently export empty cells if the relation isn't eager-loaded or if the column is computed. Test with real data containing a null relation.->modifyQuery(fn ($q) => $q->with('roles')) or the equivalent on the exporter. Export timing is the tell: a 500-row export that takes 40 seconds is almost always N+1.->formatStateUsing() is not enough — it stringifies). Check what the user sees when they sort the column in Excel; if it sorts alphabetically, it's a string, not a date.=, +, -, or @ becomes a live formula when opened in Excel. Sanitise at export time — this is a genuine security issue, not a stylistic one, and packages do not always handle it for you.memory_limit upward.storage/public disk permissions differ from the web process. Check worker logs, not web logs. Also confirm the worker's queue_connection matches the config you edited.Notification or a package-specific one. Registering both results in duplicate bells.php artisan config:cache in deploy will freeze the values — a config edit that "does nothing" is nearly always a cached config in a container built before the change.getColumns(), modifyQuery(), getFileName(), and any beforeExport/afterExport hook. getFileName() is the clean way to stamp exports with a user or date (users-2025-06-01.xlsx), which saves users from a downloads folder full of identically-named files.1.0.*) rather than ^1.0 if you depend on internals, and keep a thin application-level wrapper class so a signature change is a one-file fix.How can I help you explore Laravel packages today?