asignua/filament-xlsx-export
Filament XLSX Export adds fast, customizable Excel (.xlsx) exports to Filament resources and tables. Export filtered or selected records with configurable columns, headers, and formatting, all from a simple admin action.
Install the package and publish its config, then wire an export into the Filament table you already have. The shortest path is to add the package's export action to a resource/page and let it derive columns from the table; the first useful export is usually "current table, all matching rows, with a row number and human-readable state."
config/...php (including the streaming block) exists.rowIndex() as the first column if users need spreadsheet row numbers, and formatted() for any column where the table's display string is what people want in the file.boolean(), dates, numbers) only once the basic export is correct — the typed XLSX writer is where NULL/false and date semantics matter.Read the column-definition API and the streaming config first; those two areas account for most of the behaviour you will need to change day to day.
Derive columns from the table, override selectively. The default column set is usually right; override per column where the file needs something different from the screen. SelectColumn exports the option label the table shows — good for human-facing reports, but if a downstream system needs the stored value, define a raw column instead.
Use rowIndex() for numbering, not $rowLoop->iteration. rowIndex() numbers the file's rows from 1 regardless of which table page the user was on when they hit export. If you pass $rowLoop into a state closure, that object is counted from the file's first row on any page too — but only rowIndex() compensates for the page offset. Pick one convention per export and stick to it.
Treat the streaming route as the auth boundary. As of v1.0.1 the streaming route switches the default guard to the panel's guard, matching Filament's Authenticate middleware. That means auth(), Gate checks and query scopes written with auth() inside export code see the page's user without extra plumbing. Keep streaming.middleware limited to starting the session; do not re-implement guard switching there.
Guard-aware scopes in export queries. Because auth() resolves correctly in streaming mode, tenant/ownership scopes can be written the same way as in the resource query. For queued exports, remember the job runs outside the request: resolve and pass the user/tenant explicitly rather than relying on auth() at job runtime.
Format dates in date columns, text in formatted(). formatted() output is now treated as text and is no longer parsed back into a date. That removes the day/month swap risk, but it also means a date formatted into a string stays a string in the file. Use real date columns when the spreadsheet needs date semantics (sorting, filtering, date arithmetic).
Handle booleans with the typed column. Use boolean() (and ExportsTypedXlsx) when the file must contain real booleans. SelectColumn plus formatted() is fine for display, but typed booleans are what survive round-trips into other tooling.
Test with realistic page state. Write feature tests that export from page 2+ of a paginated table, with a mix of NULL/false booleans, a select column, a date column, and a filename containing non-ASCII characters. Assert sheet names and cell types, not just that a download was returned.
Multi-panel and guest behaviour. A guest on a table outside any panel stays in Livewire mode rather than receiving a refused streaming link, so public/guest tables degrade gracefully. In multi-panel apps, verify exports from each panel separately — the guard switch is per panel.
boolean() column reads FALSE for NULL because the core empties NULL before the typed writer sees it. Do not try to distinguish them with formatStateUsing(); the README no longer recommends that because the NULL is already gone. If the distinction matters, encode it explicitly (e.g. a state string or a separate "is set" column).false from CSV core. ExportsTypedXlsx::boolean() writes FALSE for the empty CSV cell the core produces for false. This is fixed in v1.0.1, so pin at least that version if you rely on typed booleans.formatted() is text. It will not be re-parsed as a date, and list state is formatted item by item. Don't route dates through formatted() and expect a date cell; don't rely on the old parse-back behaviour.History is reserved by Excel and becomes History 1. Sanitise dynamic sheet names and don't hard-code assumptions in tests or downstream macros.¢ and ½ no longer break downloads in v1.0.1; on older versions, sanitise filenames before export.rowIndex() compensates for the page offset; a stdClass $rowLoop received by a state closure does not (it counts from the file's first row). Mixing the two in one export produces numbering that looks off by a page.auth()), while queued exports need the user/tenant captured up front. For big tables, prefer queueing and pass identity explicitly rather than discovering the missing auth context in production.How can I help you explore Laravel packages today?