awcodes/filament-badgeable-column
Filament Badgeable Column lets you show additional context as badges alongside table column values. Add prefix or suffix badges with dynamic labels and colors, making statuses and metadata easier to scan in Filament tables.
Three steps, in this order, and you have a working badge column:
Install the matching major. The version line is tied to Filament, not to the package's own features:
| Package | Filament |
|---|---|
| 1.x | 2.x |
| 2.x | 3.x |
| 3.x | 4.x |
| 4.x | 4.x & 5.x |
On Filament 5 the safe target is ^4.2 (the 4.x line was deliberately consolidated to serve both 4 and 5). composer require awcodes/filament-badgeable-column and then check composer show rather than assuming.
Wire up the theme CSS. This is the step people skip and then file bugs about. If you use a custom Filament theme (or the standalone Tables package with its own CSS), the package's Blade views must be scanned by Tailwind:
@source '../../../../vendor/awcodes/filament-badgeable-column/resources/**/*.blade.php';
No @source line means badges render with no padding, colour or rounding — the markup is there, the classes just were never compiled.
Replace one TextColumn you already have. Start with the highest-signal column in your biggest resource — usually a status or name column:
use Awcodes\BadgeableColumn\Components\Badge;
use Awcodes\BadgeableColumn\Components\BadgeableColumn;
BadgeableColumn::make('name')
->prefixBadges([
Badge::make('status')
->label(fn (Model $record) => $record->status)
->color(fn (Model $record) => match ($record->status) {
'active' => 'success',
'inactive' => 'danger',
default => 'warning',
}),
])
->searchable()
->sortable(),
Where to look first: BadgeableColumn extends Filament's TextColumn, so nearly every method you already know (searchable(), sortable(), formatStateUsing(), weight()) still works — read the README's Usage / Badge Shape / Separator / Font sections, then keep CHANGELOG.md open, because most breaking changes in this package are Filament compatibility fixes rather than feature changes.
Prefix vs suffix. prefixBadges() puts badges between the row label and the column state; suffixBadges() puts them after. Use prefix for identity/status metadata ("this is an Active customer") and suffix for derived or secondary warnings ("Hot — low stock"). Mixing both on one column is fine but reads poorly past two or three badges.
Closures for dynamic badge sets. When the number of badges varies per record, return the array from a closure — the canonical case is a BelongsToMany:
BadgeableColumn::make('title')
->suffixBadges(fn ($record) => $record->topics->map(
fn ($topic) => Badge::make($topic->name)->color($topic->color)
))
->searchable()
->sortable(),
Per-badge label(), color(), textColor() and visible() all accept closures receiving the record, so you can keep the badge list static and push the variation into the badge itself. Prefer that when the count is stable — it keeps the closure surface small and makes the column declaration readable.
Reuse across the app. Extract badge factories into small helper methods or a dedicated class (StatusBadge::for($record)) and call them from every resource. Badge columns multiply quickly; the third copy of a match on status is where colour drift starts.
Infolists. The description explicitly covers infolist entries, not only table columns — so the same BadgeableColumn can be dropped into a ViewRecord schema to keep the detail page consistent with the list page. Build the badge set once and share it between the two.
Presentation knobs. asPills() on the column sets badge shape; separator() changes the — between the state text and the badges; fontFamily(), weight() and size() exist on both the column (styling the column's own text) and the Badge (styling the badge). A size(Size::Small) badge beside a normal-weight label is the layout that survives dense tables best.
TextColumn::getPrefix() / getSuffix() gained optional $state and $relatedRecord parameters; the package's HasBadges overrides were incompatible and produced a fatal error on load. Fixed in 3.2.1 / the corresponding 4.x release. If a Filament upgrade white-screens a table, check this package first.formatStateUsing passthrough was broken and restored in 3.2.0 / 4.1.0 — columns rendered blank or ignored formatting. Same lesson: upgrade package and Filament together, never one alone.@source is not optional. Unstyled badges after install are almost always this. Rebuild assets (npm run build) and hard-refresh; also confirm the relative path depth matches your theme file's location.$record->topics inside the closure lazy-loads per row. Eager load via ->modifyQueryUsing(fn ($query) => $query->with('topics')) on the table, or the badge column will quietly make a 30-row table issue 30 queries.Badge::make() takes a name that behaves like a key — duplicates are deduped, and the name is the fallback label. Give each badge a distinct name (e.g. 'status', 'hot') even when you override label().->visible() on anything past N) and rely on asPills()/size(Size::Small) for density. Long badge lists historically got wrapping support — worth checking the current API before hand-rolling a wrap div.$record->status ?? 'unknown') rather than trusting the package to absorb nulls.->weight(FontWeight::Bold) on the column bolds the state text; on the Badge it bolds the badge. The README calls this out explicitly and it's still the number one "why won't it style" question.BadgeableColumn (or the trait it uses) if you need house-standard defaults — e.g. always asPills() and always Size::Small — instead of repeating the chain across forty resources.How can I help you explore Laravel packages today?