alperenersoy/filament-export
Filament Export lets you add Excel and CSV export buttons to Filament resources and tables. Customize columns, formatting, and file names, and filter which records are exported, so admins can download data straight from the panel.
Install and publish
composer require alperenersoy/filament-export
php artisan vendor:publish --tag="filament-export-config"
php artisan vendor:publish --tag="filament-export-views" # only if printing/custom markup matters
The package needs a Filament panel already set up; ext-zip is required if you enable XLSX output (PhpSpreadsheet is pulled in for spreadsheet writing). Check composer show alperenersoy/filament-export against your Filament major — the action namespaces and registration style differ between Filament 2, 3, and 4, so pin the package version that matches your panel.
First use case: a filtered CSV export on a resource
Add the header action to any resource table. It reads the table's current query, so search, filters, and sorting are respected:
use AlperenErsoy\FilamentExport\Actions\FilamentExportHeaderAction;
use AlperenErsoy\FilamentExport\Actions\FilamentExportBulkAction;
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')->searchable()->sortable(),
TextColumn::make('email')->searchable(),
TextColumn::make('created_at')->dateTime(),
])
->headerActions([
FilamentExportHeaderAction::make('export')
->label('Export')
->fileName('users-' . now()->format('Y-m-d'))
->formats(['csv', 'xlsx']),
])
->bulkActions([
FilamentExportBulkAction::make('export')
->label('Export selected'),
]);
}
Run php artisan config:clear after publishing the config, then hit the button. The modal lets the user pick format and columns, and offers Export/Print.
Where to look first
config/filament-export.php — defaults for format, CSV delimiter/enclosure/BOM, date/time formats, preview behaviour, page orientation.vendor/alperenersoy/filament-export/src/Actions/FilamentExportHeaderAction.php — the authoritative list of ->methods() for the version you actually installed. The API has shifted across Filament majors; the source is faster than guessing.Header vs. bulk action. Use FilamentExportHeaderAction when the user should export "what I'm currently looking at" (respects table filters/search). Use FilamentExportBulkAction when they should export exactly the checked rows. The bulk action exports the selected records only, so its scope follows your table's selection config (selectCurrentPageOnly, etc.) — document that for users who expect "select all across pages".
Curation beats configuration. The column picker is populated from the table columns. Shape the export by shaping the table: use TextColumn::make('status')->getStateUsing(fn ($record) => $record->status->label()) so enums export as human labels instead of raw values, and format dates via the action's dateFormat() / timeFormat() rather than in every accessor.
Additional/computed columns. For values that aren't real table columns (full address, balance, related counts), define them as additional columns on the action. This keeps the table clean while still giving finance/ops the extra fields. Verify the exact signature in your installed version — the additional-columns API is one of the parts that changed between majors.
Print instead of PDF. The print path renders an HTML table in a print-friendly view and hands off to the browser's print dialog. That's usually the right call internally: no PDF binary, no font headaches, and users can "Save as PDF" if they need a file. Publish the views to add a logo, letterhead, footer, or company header, and set page orientation to landscape for wide tables.
Reusable action factory. For apps with a dozen resources, don't copy the same five chained calls. Add a small helper:
// app/Filament/Support/ExportActions.php
public static function make(string $name, string $file): FilamentExportHeaderAction
{
return FilamentExportHeaderAction::make($name)
->fileName($file)
->formats(['csv', 'xlsx'])
->dateFormat('d/m/Y')
->timeFormat('H:i');
}
Authorization. Wrap the action with ->visible(fn () => auth()->user()->can('export', User::class)) or a policy check. The action exports whatever columns the table exposes, so visibility is the only thing standing between a curious user and a CSV of every column you left on the table.
Large datasets. Prefer CSV over XLSX once you're past a few tens of thousands of rows — XLSX via PhpSpreadsheet is memory-hungry and synchronous. For genuinely large exports, constrain the action's query or move the export to a queued job; this package generates the file on the request, not in a worker.
Audit trail. There's no event system to hook, so if you need "who exported what", wrap the export behind your own action or log inside a custom action class that calls the same export service. Treat exported files as sensitive data and avoid leaving them in public storage.
CSV formula injection. Values beginning with =, +, -, or @ can be interpreted as formulas when opened in Excel. If users can type into fields that end up in an export, sanitize those values (prefix with a single quote or a tab) in an accessor before they reach the CSV writer.
Excel and non-ASCII characters. If accented or Turkish characters look garbled when opening the CSV in Excel, enable the BOM option in config/filament-export.php (csv_bom / equivalent) or set the delimiter to ; for locales where comma is the decimal separator. Both are config-level, not per-action.
Dates are not magically formatted. dateFormat() / timeFormat() apply to values the package recognizes as dates (Carbon casts, date columns). Raw strings from a selectRaw or a joined column pass through untouched — format them at the accessor level.
Relation and joined columns. Columns backed by relationships may not resolve in the export query the way they do in the table row. If a relationship column exports blank, add an accessor on the model or a getStateUsing on the column, and confirm the export query actually selects the needed data.
Preview route and popup blockers. If preview is enabled, the export first renders a preview page with download/print controls; print opens a browser print context. Users with aggressive popup blockers will report "nothing happens" — that's usually the browser, not the package. directDownload()/disablePreview() is the fix for teams that just want the file.
Config cache. After changing config/filament-export.php in production, run php artisan config:cache (or config:clear locally). A stale cached config is the most common reason a changed delimiter or default format appears to be ignored.
Filenames. Keep fileName() free of slashes and characters that break Content-Disposition; the package appends the extension based on the chosen format. Non-ASCII filenames can behave differently across browsers and proxies.
Sensitive columns. The column picker lists the table's columns. Password hashes, tokens, internal notes, and PII should never be table columns in the first place, but double-check before shipping an export button to a broad audience.
Version drift. The package supports multiple Filament majors, and header/bulk action registration changed in Filament 4 (actions moved under Filament\Actions). If the action doesn't appear, confirm the namespace matches your Filament version and that the action is returned from headerActions()/bulkActions() rather than actions(). When in doubt, read the installed source and the tag matching your composer.json constraint rather than the README on main.
Extension points. Published Blade views are the supported way to customize the print/preview markup. Subclassing the action is the cleanest way to bake in company-wide defaults; avoid editing vendor files, since the next composer update will erase the change.
How can I help you explore Laravel packages today?