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 Enum Field Laravel Package

datomatic/nova-enum-field

A Laravel Nova field for working with PHP enums—display them as badges or labels, edit via select inputs, and filter Nova resources by enum values. Supports backed and pure enums, nullable values, and customizable colors for clean, type-safe admin panels.

View on GitHub
Deep Wiki
Context7

Getting Started

Install, then verify compatibility before you write a line of code.

composer require datomatic/nova-enum-field

Two gates matter more than the feature list:

  1. PHP 8.1+ — the package renders native enum cases; there is no polyfill path.
  2. Nova major version — check the package's composer.json constraint against your installed Nova major (4 vs. 5) before installing. This is the single question that decides whether the package is usable in your app.

Pin ^1.11.1 or later. That tag fixes exception #8, thrown when EnumFilter / EnumBooleanFilter is applied to a resource index rendered through a relation (e.g. an "Orders" panel on a customer detail page). Most admin panels eventually hit that path.

Define a backed enum that implements HasLabel.

The field needs a string or int backing value, and labels come from Nova's own contract — not from a package-specific interface:

use Laravel\Nova\Fields\HasLabel;

enum Status: string implements HasLabel
{
    case Draft = 'draft';
    case Published = 'published';

    public function label(): string
    {
        return match ($this) {
            self::Draft => 'Draft',
            self::Published => 'Published',
        };
    }
}

Wire it into a resource and its filter in the same commit.

use Datomatic\NovaEnumField\Enum;
use Illuminate\Validation\Rule;

Enum::make('Status')
    ->enum(Status::class)
    ->rules('required', Rule::enum(Status::class));
use Datomatic\NovaEnumField\EnumFilter;

public function filters(Request $request): array
{
    return [EnumFilter::make('status')->enum(Status::class)];
}

Where to look first.

  1. The README usage block — it covers the whole public surface.
  2. src/ — the package is deliberately thin: Fields/Enum.php, Filters/EnumFilter.php, Filters/EnumBooleanFilter.php. Treat the source as the documentation; it is a few hundred lines and readable in a coffee break.
  3. tests/ — the best reference for the exact field-name/meta contract Nova expects, and the filter tests are the canonical example of the relation-index case.

First real use case: delete a hand-maintained options array.

// before — two sources of truth, drift guaranteed
Select::make('Status')->options([
    'draft' => 'Draft',
    'published' => 'Published',
]);

// after — one source of truth, reused by index, detail, form, and filter
Enum::make('Status')->enum(Status::class);

The payoff is not the field itself; it is that index tables, detail views, create/update forms, and filters all resolve the same label() values from the same enum.

Implementation Patterns

Keep the model cast as the source of truth and let the field follow it. This is the pattern everything else hangs off:

protected $casts = ['status' => Status::class];

With the cast in place, filter comparisons, index display, and validation all flow through the enum rather than raw strings. The field becomes a presentation adapter, not a second place to declare valid values.

Use stock Nova modifiers — the field composes with the normal field API. Resist the urge to look for package-specific flags; there usually aren't any:

Enum::make('Status')
    ->enum(Status::class)
    ->hideWhenCreating()
    ->hideFromIndex()
    ->help('Set automatically after review');

Pair with Rule::enum for validation. The package renders the select; it does not replace Laravel's validation rule:

->rules('nullable', Rule::enum(Status::class))

This rejects any stored value that isn't a valid case — which is exactly the failure mode you get when someone edits a row outside Nova or a background job writes a stale string.

Nullable statuses need all three pieces aligned. ->nullable() on the field, 'nullable' in the rules, and a nullable column in the migration. The filter's null bucket is only meaningful if the underlying column can actually hold null — check the migration, not just the field declaration.

Pick the filter class by cardinality, not by habit.

  • Multi-case enum → EnumFilter.
  • Binary/boolean-shaped enum → EnumBooleanFilter.
use Datomatic\NovaEnumField\EnumBooleanFilter;

EnumBooleanFilter::make('is_active')->enum(ActiveState::class);

The wrong class renders perfectly fine and then silently never narrows the query — a bug that costs an afternoon because nothing errors.

Reuse the same filter instance in metrics and cards. Because the filter emits backing values rather than labels, EnumFilter::make('status')->enum(Status::class) works identically in resource filters and in any card or metric that queries by status. No translation layer, no parallel mapping table.

Treat relation-rendered indexes as a first-class case. Filters mounted on a HasMany / MorphMany related resource (one with its own filters()) travel a different request shape than a top-level index. Since 1.11.1 both filter classes handle it. If you pin an older tag or maintain a fork, this is the first suspect for a hard 500 on an otherwise-fine filter.

For DB-level sorting, keep an explicit sortable column. In grouped index views the enum's label() drives headers, which is what you want. But Nova's default sort on an enum-cast attribute doesn't map cleanly to SQL — sort on the underlying column.

Extend through withMeta or a subclass. Anything the fluent API doesn't expose can go through meta, or you can override component() on a subclass:

Enum::make('Status')
    ->enum(Status::class)
    ->withMeta(['placeholder' => 'Choose…']);

Split your tests along the same seam as the package.

  • Unit-test the enum itself: Status::from(), label() for every case.
  • Use Nova's Nova::filtered()-style helpers to assert the filter returns the correct query constraint.
  • Add one test for an enum filter on a relation-mounted resource. That is the regression class fixed in 1.11.1 and the path most likely to break across Nova upgrades.

Day-to-day checklist for a new enum-backed column. Once you've done it twice it takes minutes:

  1. Add the column (typed consistently with the enum backing).
  2. Add the cast to the model.
  3. Implement HasLabel on the enum with a match arm per case.
  4. Enum::make(...)->enum(...)->rules('nullable', Rule::enum(...)) in fields().
  5. Register the right filter class in filters() and confirm it's reachable via availableFilters().
  6. Smoke-test the index, the form, and — if the resource appears nested — the relation-rendered index.

Gotchas and Tips

  • Upgrade if you filter related indexes. v1.11.1 fixes exception #8 when EnumFilter / EnumBooleanFilter is applied to an index rendered through a relation. The tell: a 500 on a nested/detail index while the resource's own index works fine. No config changes needed — it's a straight bug fix.
  • Backed enums only. Pure/unit enums have no scalar value for the filter's whereIn. The field will render and look correct while the filter silently fails to narrow. Back with string at minimum.
  • Label resolution order. Nova resolves HasLabel::label() first, then falls back to the case name. If labels read Draft-with-a-fallback feel, verify the interface is actually implemented on the enum — a description() alone won't drive select options.
  • Int-backing in a string column. An int-backed enum stored in a VARCHAR column passes validation fine but produces filter queries that never match. Audit the migration before you start debugging the field.
  • Never mix ->options() with ->enum(). enum() populates the options internally; a later options() call quietly overwrites them. You will then chase a phantom mapping bug for an hour. Grep for both calls on the same field.
  • Filter registration is two steps. The filter must be returned from Resource::filters() and the resource must be reachable via availableFilters(). A missing filter simply disappears from the UI with no error — check reachability before assuming the class is broken.
  • Blank field / no styles. Compiled assets ship in the package's dist/; publishing is not required. If the field renders as an empty box, run php artisan nova:publish and clear the Nova cache before filing a package bug.
  • Debug via meta. The field's meta JSON (Nova's debug output or the Vue inspector) shows whether options and labels map correctly — far faster than reading the compiled component.
  • Confirm namespaces against src/. The classes are thin enough that copying a snippet's namespace from a blog post is a real risk. Verify against the source and composer.json autoload.
  • Classify this as a vendored utility, not a strategic dependency. 19 stars, zero dependents, single maintainer. Pin the version, run composer update in a branch, and keep a fork as a realistic plan — the surface is a few hundred lines, so maintenance in-repo is cheap and low-drama. The 1.11.1 patch is a positive signal about triage, but a patch release doesn't upgrade the risk class.
  • Don't adopt it for one or two columns. Below roughly three enum-backed columns, a plain Select with an options array is about twenty lines and not worth a dependency, a version pin, or a fork plan. Adopt when the repetition starts to hurt.
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