giacomomasseron/filament-async-column
Filament Async Column loads table column values asynchronously, so slow or expensive data (API calls, heavy queries, computed stats) no longer blocks your Filament tables from rendering. Cells show a placeholder and fill in when ready, keeping admin lists fast and responsive.
What it is, in TPM terms. giacomomasseron/filament-async-column is a Filament v5 table column that lets a cell paint before its value resolves. You swap Column::make() for AsyncColumn::make(), supply a per-record callback via resolveUsing(), and the cell renders a placeholder until the callback returns. It is a presentation-layer extension of Filament's column lifecycle — not a queue, not a cache, not a batching layer. Everything a TPM plans around should assume it changes perceived performance and loading-state ownership, not call volume.
Architecture fit. Narrow but genuine. The failure mode it addresses — a paginated admin table where each row triggers a remote or expensive lookup (carrier ETAs, enrichment scores, third-party pricing), so the whole table blocks on the slowest row — is recurring in the panels we build. This localizes the degradation to the affected cells and unblocks roadmap items that were previously gated on a caching/batching initiative. The v1.1.2 release is purely additive: four CSS loader presets (skeleton default, spinner, dots, bar) plus loaderColor() resolving through Filament's palette including FilamentColor::register() entries and dark mode. No change to the resolution contract.
Integration feasibility. High, per column. One Composer dependency, one class swap on targeted fields, three chained methods. Release notes state explicit backward compatibility — loadingState() retains precedence, and columns using neither new method render the same skeleton as v1.1.1 — so the version bump is a schedulable no-op independent of feature adoption. That decoupling is the single most useful property for planning: the dependency can ride a routine maintenance window while loader adoption becomes its own scoped decision.
Technical risk.
resolveUsing() fires once per page load or on every sort/filter/search/pagination/Livewire poll determines the real call multiplier. Nothing in the provided materials documents this..gitattributes-trimmed dist), but bus factor is the long-term exposure, not code quality.loadingState() markup wins over loader(). This prevents regressions but means a design spec calling for spinners will silently not render on any table already using custom loading markup.prefers-reduced-motion, and color resolution follows dark mode. That closes a recurring audit finding we would otherwise ticket.Key questions.
resolveUsing() fire once per page load, or per re-render (sort, filter, search, paginate, poll)? What is the measured call multiplier on a realistic table?loaderColor() fail loudly or fall back silently on an unregistered color?loadingState() markup, and who owns updating those?Stack fit. Laravel + Filament v5 + Livewire tables. No new runtime, queue, or service boundary — the package lives entirely inside the existing column API, so no new mental model for engineers already fluent in Filament columns. The v1.1.2 surface is loader(), loaderColor(), and the pre-existing loadingState() escape hatch. All three accept closures evaluated per row, which is what lets design express conventions ("spinner in primary for externally-fetched values") as a documented rule rather than per-table code.
Migration path. Near-zero friction. v1.1.1 → v1.1.2 renames nothing, changes no defaults, and preserves the default skeleton for untouched columns. Plan it as two separate line items: the dependency bump (no-op diff, routine window) and loader adoption (opt-in UX decision, can trail by a sprint). Conflating them turns a five-minute bump into a design review.
Compatibility.
loadingState() usage. Custom markup continues to win over loader() — protects us from regressions, but sets an expectation gap with design.loaderColor() against our palette in light and dark, especially custom FilamentColor::register() entries. Panel color registration must occur before table render, and low-contrast tints are the likely cosmetic defect.loader()/loaderColor() closures stay side-effect free. They are styling decisions, not a second place to perform I/O — this is the easiest way to accidentally double per-row calls.Sequencing.
loadingState() holdouts explicitly rather than assuming presets apply.Maintenance. Low code surface, MIT, additive-only recent releases. The real maintenance cost is not upgrading the package — it is the surrounding work the package does not do: caching, throttling, and retry policy for the underlying calls. Budget for that in the same initiative, or the perceived-performance win gets spent immediately on real load.
Support. Support load concentrates in two places: ambiguous skeletons (a cell that never resolves reads as a broken table, and generates tickets indistinguishable from a genuine outage) and missing spinners on tables where loadingState() silently wins. Both are eliminated by documentation and a clear convention, not by code. Also note the dependency is single-maintainer: we should pin to a tested minor, track upstream activity, and keep a documented one-afternoon fallback (revert to a standard column with a synchronous or preloaded value).
Scaling. The package scales with the number of columns, not with data volume — and that is the wrong axis to optimize for free. Every additional AsyncColumn multiplies per-row remote calls by re-render count. Scaling guidance is therefore: adopt on a small number of genuinely slow columns, never as a default column type, and treat any expansion as requiring a caching story first. Also: does not batch, queue, or limit concurrency. Table-level or bulk async enrichment is a different architectural problem and should be scoped separately.
Failure modes.
loadingState() exists.loaderColor() in one of the two themes.Ramp-up. Effectively zero for anyone who has written a Filament column: one method chain, no new concepts, no frontend build step (pure CSS, no JS bundle). The learning is not technical, it is convention — designers and engineers agreeing which loader means what, and reviewers knowing that closure bodies stay side-effect free. Plan a short written convention alongside the pilot rather than training. Stakeholder framing should stay narrow and honest: this is a small additive release that keeps data-heavy admin tables responsive when individual values come from outside services, it gives us accessible, theme-aware loading states we would otherwise build and own, it is backward compatible with no migration and no downtime — and the caveat is dependency risk, not technical risk.
How can I help you explore Laravel packages today?