Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Badge Field Laravel Package

timothyasp/nova-badge-field

A Laravel Nova field for displaying colorful badges in your admin panel. Easily show status labels, counts, or tags with customizable colors and styles, ideal for resource index and detail views.

View on GitHub
Deep Wiki
Context7

Getting Started

Install into a Nova app (Nova 3.x era package; verify the constraint against your Nova major before committing):

composer require timothyasp/nova-badge-field
php artisan nova:publish   # Nova 3 asset pipeline

No service provider registration is required — the field is auto-discovered via composer.json extra.laravel.providers (or Nova's field discovery). Where to look first, in this order:

  1. src/Badge.php — the field class. This tells you the exact fluent API (map, labels, icons, defaults) far more reliably than the README, which is thin.
  2. resources/js/components/IndexField.vue and DetailField.vue — the render logic. If you need to know what happens on an unmapped value or how colors are applied (class vs inline style vs badge-*), it's here.
  3. dist/js/field.js — the compiled asset. If this is stale relative to resources/js, you'll be debugging a build problem, not your field config.

First use case — a status column on an index table:

use Timothyasp\Badge\Badge;

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),

        Badge::make('Status')
            ->map([
                'draft'     => 'orange',
                'review'    => 'blue',
                'published' => 'green',
                'archived'  => 'gray',
            ])
            ->exceptOnForms(),   // display-only; the form needs a Select, not a badge
    ];
}

That single field replaces the usual ->displayUsing(fn ($v) => ucfirst($v)) + custom CSS dance on index/detail screens.

Implementation Patterns

Map is the whole API. Treat map as the single source of truth for label and color, and derive it from a PHP enum or a model constant so it can't drift:

Badge::make('Status')
    ->map(Status::colorMap())      // ['draft' => 'orange', ...]
    ->exceptOnForms(),

Enums and cast values. The map is keyed on the resolved value, not the raw attribute. If your model casts to an enum, resolve to the backing string/->name explicitly:

Badge::make('Status')
    ->resolveUsing(fn ($value) => $value?->value)
    ->map(Status::colorMap()),

Mismatched key types are the #1 cause of "everything renders gray" — 0 vs '0', 'active' vs 'Active', trailing whitespace from a legacy column.

Computed / virtual statuses. Because resolveUsing runs server-side, you can badge something that doesn't exist as a column — e.g. a derived health flag:

Badge::make('Health')
    ->resolveUsing(fn ($resource) => $resource->last_seen_at?->gt(now()->subDay())
        ? 'healthy'
        : 'stale')
    ->map(['healthy' => 'green', 'stale' => 'red'])
    ->onlyOnIndex(),

Note $resource is available in the callback closure on Nova 3.27+ / 4.x; on older versions you only get the value, so precompute via an accessor on the model instead.

Multiple badges from one column. For JSON/tag columns, either point the badge at a single derived value, or stack sibling Badge fields with ->onlyOnDetail() and ->hideFromIndex() so the index doesn't explode into a wall of pills.

Pair with a real field for input. The badge should never be the editing surface. Keep the badge ->exceptOnForms() (or ->onlyOnIndex()->onlyOnDetail()) and add a Select/Radio for the actual write path — two fields, same attribute, different contexts.

Filtering and sorting. Out of the box this is a display field. To let users sort/filter by status, add a second, hidden field on the same attribute:

Select::make('Status')->onlyOnForms()->filterable(),

Badge::make('Status')
    ->map(Status::colorMap())
    ->exceptOnForms()
    ->sortable(),   // works only if the column stores the value directly

->sortable() is a pass-through on the underlying column, so it breaks for computed values. For those, don't offer sorting rather than offering a lie.

Detail vs index tuning. Use ->onlyOnIndex() with a compact map (color only, short labels) and a verbose ->onlyOnDetail() variant with icons if your build supports them.

Gotchas and Tips

Nova 4+ ships a native Badge field. Laravel\Nova\Fields\Badge supports map, labels, icons, withIcons, and types — nearly a superset of this package, maintained by Laravel. On a greenfield Nova 4/5 project, use the native field. Reach for this package only if you're pinned to Nova 3 or you genuinely prefer its rendering. Given the last release is mid-2022, assume no Nova 4/5 compatibility work has happened; check composer.json's laravel/nova constraint before you plan around it.

Stale compiled assets. If you change resources/js and see no difference, you're looking at dist/. Either run the package's build (check package.json — it's an older Laravel Mix/webpack setup, not Vite) or fork and rebuild. On Nova 4 the asset pipeline changed to Vite and a Mix-built dist will simply not be loaded correctly.

Unmapped values. Decide up front what an unknown value should do. Most badge fields fall back to a neutral/gray pill showing the raw value; some render empty. Confirm in IndexField.vue before you ship, otherwise a stray status value silently disappears from the UI and nobody notices for a month.

Color vocabulary is not magic. Colors usually map to Tailwind or Nova utility classes baked into the component. Custom hex values, or arbitrary classes your theme purges (Nova 4's Tailwind build only includes classes it finds in source), won't render. Stick to the color names the component's classes/computed section actually defines.

Null and empty state. A null badge is an empty cell, which reads as "field missing" during debugging. Either ->default('unknown') via resolveUsing, or ->nullable() semantics plus a '—' fallback in your map.

Debugging checklist when a badge renders blank or wrong:

  1. ->resolveUsing(fn ($v) => tap($v, fn ($x) => logger()->info(gettype($x).': '.var_export($x, true)))) — confirm the resolved value and its type.
  2. Confirm the field isn't being swallowed by a form view (onlyOnDetail on an index screen = invisible).
  3. Check field.value in Vue DevTools — if it's populated but unstyled, it's a color/class problem, not a data problem.
  4. If nothing renders, check the asset is registered (Nova::script or auto-discovery) and that nova:publish ran after install.

Extension points worth knowing before forking: the field's component() can be swapped for your own Vue component if you want richer rendering (multi-color, tooltips, click-through links) without abandoning the fluent PHP API. Publishing the package's views/components into your app and overriding them is cheaper than a fork if you only need a markup tweak — but remember to re-publish on package upgrades.

Maintenance risk. 58 stars, zero dependents, no release since 2022. That's fine for a ~200-line display field — the blast radius is small and the class is easy to vendor into app/Nova/Fields/. Budget half a day to copy it in-house rather than treating it as a long-lived dependency; you'll want ownership of the Vue component the moment your design system diverges from Nova's defaults.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource