shreejan/actionable-column
Add interactive action buttons to Filament table columns: badges or plain text with connected action buttons, custom icons and colors, clickable columns, and empty-state "+ Add" support. Works with any Filament Action and Filament v4/v5.
Architecture fit (strong, narrowly scoped). ActionableColumn is a subclass of Filament's TextColumn, not a parallel rendering system. That is the right architectural shape for this problem: it inherits the entire TextColumn API (searchable(), sortable(), formatStateUsing(), date(), copyable()), so it slots into existing resource table schemas as a drop-in substitution rather than an adjacent abstraction. Critically, the actions are Filament-native Action objects, not a bespoke handler. Authorization policies, modal schemas, requiresConfirmation(), visible()/authorize() gating, notifications, and audit hooks all flow through the framework's own action pipeline. There is no domain-layer coupling, no service provider, no migrations, and no publishable config beyond a CSS path override. The blast radius of adoption is one class import per column.
Integration feasibility (very high, low effort). One composer require, one use statement, and a swap of TextColumn::make('status') → ActionableColumn::make('status') plus a ->tapAction(...). The public API is roughly eight methods and the README documents all of them in a single table — the documentation-to-surface-area ratio is genuinely good. For a Filament-fluent engineer this is an afternoon including QA, not a sprint.
Technical risk (moderate-to-high, concentrated in one place). The code risk is low; the supply-chain risk is the headline. 16 stars, zero dependents, single maintainer, and a 1.x line whose most recent release (v1.1.1) was a composer.json versioning/PHP-compatibility chore — meaning the constraint declarations are still settling. v1.1.0 retrofitted Filament v5 support with "no code changes needed," which is a green flag for API stability but also signals that compatibility is declared rather than tested against a matrix. There is no evidence of a CI test suite. v1.0.1 fixed a real operational bite: published CSS in public/css/ was being clobbered by composer install/update. That fix (custom CSS loading last from resources/css/) is the pattern to adopt from day one, but its existence tells you the package has had at least one asset-lifecycle defect in production. Note also the release metadata date (2026-06-12) reads as forward-dated relative to the changelog sequence; treat the versioning/timeline data as unreliable and pin explicitly rather than trusting "latest."
Behavioral surprises worth flagging to stakeholders. The empty-state "+ Add" button is not suppressible — showActionIcon(false) hides the icon but the empty-state affordance always renders, and there is no hideEmptyButton(). Any column you attach an action to will advertise a creation path to users on null rows. That is a product decision the package makes for you; validate it against each column's semantics before rollout.
Key questions to resolve before adoption:
composer require resolve cleanly against our existing composer.lock, or does it attempt to move Filament's major? (If the latter: hard stop, pin ^1.1.)Action (so we attach the same instance to row actions and the column, rather than duplicating authorization logic)?tapAction guaranteed to return an Action for every record, and does the column degrade gracefully when one returns null?Stack fit. Laravel + Filament v4 or v5 + Livewire. No additional runtime assumptions — no queue, no cache driver, no storage. Composer constraint is ^4.0|^5.0 for Filament, which means a single install can satisfy either major. Because the column extends TextColumn, it composes with Filament's existing table concerns (filters, bulk actions, column managers) without special handling.
Migration path (additive, reversible, per-column). There is no migration in the data sense — nothing to backfill, no schema change. The migration is a code migration executed one column at a time: ActionableColumn and TextColumn coexist happily in the same table() definition, so you can convert a single high-value column and leave the rest untouched indefinitely. Reverting is a one-line swap back. This is as close to zero-commitment as a dependency gets.
Compatibility. Two constraints to verify at install time, not from the README: (a) the resolved Filament major in composer.lock after install, and (b) the PHP version floor declared in the 1.1.1 composer.json. Run composer why-not / inspect the lock diff before merging. If either drifts, pin the package rather than the framework — the package is the disposable side of the relationship.
Sequencing (four gates, roughly a week of elapsed time for a small team):
| Stage | Action | Exit criteria |
|---|---|---|
| 1. Resolve | composer require, inspect lock diff, pin the version |
No Filament major movement; version pinned in composer.json |
| 2. Pilot | Convert one status/state column on a moderation or approval table | Modal pre-fills, action mutates, table refreshes, policy enforced |
| 3. Standardize | Extract shared Action factories to the resource (or dedicated action classes); attach the same instance to row actions and column |
No duplicated authorization or form definitions |
| 4. Roll out | Convert remaining columns; establish resources/css/actionable-column-custom.css as the only styling surface |
Custom CSS auto-detected; public/css/ never hand-edited |
Two sequencing rules that matter: never define an action twice (extract it first — this is where the package actually pays for itself, since policies and modals carry over untouched), and never edit published assets (the custom-CSS file is auto-detected and loads last, and it is the documented escape hatch for the class of bug that v1.0.1 fixed).
Deployment note. The CSS override path is env-configurable (ACTIONABLE_COLUMN_CUSTOM_CSS_PATH). Set it explicitly in each environment's .env rather than relying on auto-detection — auto-detection on a build pipeline where resources/ is compiled differently across environments is exactly the kind of thing that works locally and silently fails in staging.
Maintenance (near-zero for us, non-zero for them). No migrations, no config to publish, no scheduled work, no background processes. The ongoing cost is version-tracking: someone must watch whether the maintainer ships a Filament v6-compatible release within a reasonable window of v6 landing. That is a calendar reminder, not an engineering task — until it isn't.
Support. Single maintainer, GitHub issues, security contact by email. No SLA, no commercial backing, no deprecation policy. For a UI affordance on internal admin panels, that is an acceptable risk profile. For anything customer-facing with an uptime commitment attached, it is not — and the correct response is to vendor the one Blade view plus the one column class rather than depend on upstream responsiveness.
Scaling. The column render itself is cheap — it is a TextColumn with extra markup. The real scaling exposure is what you attach: relationship-backed Select components inside the tap action's schema, visible() closures that touch relations, and formatStateUsing() closures that lazy-load. All of these run per row and are a straightforward N+1 waiting to happen on a 500-row table. Two mitigations: eager-load in the resource's modifyQueryUsing(), and keep visible() closures dependent on already-selected columns only. clickableColumn() on a wide table also enlarges the hit target across the whole cell — fine for a status column, a misclick generator on a dense financial table.
Failure modes, ranked by likelihood:
composer update overwrites published assets — already happened once (fixed in v1.0.1). Mitigation: never touch public/css/; use the documented custom CSS file.tapAction closure returns null for some records — the class signature implies an Action return; unverified degradation behavior. Mitigation: pilot explicitly exercises the null path.Ramp-up. Under two hours for a developer already fluent in Filament tables; the eight-method API is fully documented. The non-obvious knowledge — the empty-state behavior, the CSS override location, the shared-action extraction pattern — is exactly the content of this assessment and should live in the team's integration notes, not in one engineer's head.
Recommendation. Adopt, narrowly and pinned, for internal admin panels where the pattern is genuinely "the column is the state transition." The decision is low-stakes in both directions: the in-house equivalent is roughly one class plus one Blade view, so the build-vs-buy question is close to a coin flip, and the vendor-out risk is bounded by that same fact. Treat the pilot's exit criteria as a hard gate, keep the shared-Action extraction as a team standard so the package's real leverage (inherited authorization and modal plumbing) is actually captured, and set a standing reminder to evaluate a fork at the next Filament major.
How can I help you explore Laravel packages today?