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

Filament Model States Laravel Package

filamerce/filament-model-states

Filament plugin for managing Laravel model states directly from your admin panel. View current states, transition models through their state machine, and add state columns, badges, and actions—integrating with spatie/laravel-model-states for clean, typed state management.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require filamerce/filament-model-states
php artisan vendor:publish --tag="filament-model-states-config"

The package is a thin Filament bridge over spatie/laravel-model-states — you still define your state machine the Spatie way (class Draft extends State { public static $name = 'draft'; } plus Transition classes and a HasStates model), and this package supplies the Filament presentation layer for it.

Where to look first, in order:

  1. config/filament-model-states.php — the color/icon maps that drive badges and transition buttons. This is the file you will touch most.
  2. The README's StateColumn / StateSelect examples — those two components are ~90% of real usage.
  3. The package's service provider (grep -rn "FilamentIcon::register" vendor/filamerce/filament-model-states/src) — as of v2.2.0 this is where the icon alias list lives, and reading it is the fastest way to learn the alias names you can override.
  4. Your own state classes — anything the UI shows (color, icon, label) is resolved from methods/attributes you put there.

First use case — a table badge plus a transition action:

use Filamerce\FilamentModelStates\Columns\StateColumn;
use Filamerce\FilamentModelStates\Forms\Components\StateSelect;

// OrderResource
public static function table(Table $table): Table
{
    return $table
        ->columns([
            StateColumn::make('state'),          // renders label + color + icon
        ])
        ->actions([
            Action::make('transition')
                ->form([StateSelect::make('state')])
                ->action(fn ($record, array $data) => $record->state->transitionTo($data['state'])),
        ]);
}

Make sure the model casts the attribute (protected $casts = ['state' => OrderState::class];) — without the cast the column receives a raw string and renders nothing useful.

Implementation Patterns

Badge-first resources. The dominant pattern is read-only state display in the table and all mutation happening through actions. Keep StateColumn on the table and put the transition affordance in a row action rather than an inline editable select; it keeps the audit trail (Spatie fires events on transition) meaningful.

Drive the select from the state machine, not the enum. StateSelect should be fed the transitions actually legal from the current state. If the package's select exposes the full state list, constrain it with a closure over $record->state->transitionableStates() (or your own allowedTransitions() helper) so users can't submit an illegal transition and eat a TransitionNotFound exception.

Group transitions into a single action. Instead of one button per transition (which multiplies as the state machine grows), expose a ActionGroup or a select-based action whose options are the legal target states. Confirmation modals belong on destructive transitions (cancelled, refunded); a ->requiresConfirmation() on all of them trains users to click through.

Bulk transitions carefully. Bulk actions run per-record and each fires a transition — wrap in a transaction only if your domain allows it, and ->deselectRecordsAfterCompletion() so the table refreshes correctly. A failed transition mid-batch should surface which records were skipped.

Notifications close the loop. After transitionTo(), send a Filament notification with the new state's color/label pulled from the same resolver the column uses, so the vocabulary stays consistent.

Theme icons globally (new in v2.2.0). Previously the badge/action icons were baked into the package. Now they're registered as Filament icon aliases, which means you can swap them for your icon set in one place:

use Filament\Support\Facades\FilamentIcon;

public function boot(): void
{
    FilamentIcon::register([
        // check the package provider for exact alias keys — they follow
        // the `filament-model-states::…` convention
        'filament-model-states::state-column' => 'heroicon-o-flag',
    ]);
}

Do this in a service provider that boots after the package's, and remember php artisan filament:icon-cache if you cache icons in production — a stale cache is the usual reason an override "doesn't work."

Integration with policies. Gate the transition action on ->visible(fn ($record) => auth()->user()->can('transition', $record)). The package renders what you give it; it does not consult your policies, so authorization is entirely your responsibility.

Gotchas and Tips

  • Raw strings vs. state objects. The single most common failure: a missing cast, or a ->formatStateUsing() that receives null for records created before the state column existed. Backfill or provide a default state before shipping the column.
  • Colors are Filament palette names, not hex. success, warning, danger, info, gray, primary resolve; #ff0000 does not render as a badge color. Custom branding goes through a panel palette or a custom getColor() returning a registered name.
  • Filtering and sorting are on the stored class string. The DB holds the state class name, so a naive SelectFilter won't match user-friendly labels. Build the filter options from State::all() with labels as keys, and be aware alphabetical sorting sorts class names, not human labels — use a sortable() closure with a CASE expression if ordering matters.
  • Refresh after transition. Filament's action closure mutates the model instance, but cached table state can show the old badge. $record->refresh() (and returning the record from the action) avoids the "clicked it twice because nothing changed" bug.
  • Alias override order and caching. v2.2.0's icon aliases are registered by the package provider; FilamentIcon::register() in your own provider wins only if it runs later. If the alias isn't taking effect, check provider order first, then filament:icon-cache, then whether the panel actually uses the icon set you named.
  • Alias names are not documented in the release notes. The PR exposes them, but the changelog doesn't enumerate them. Grep the provider for the exact keys before wiring overrides into a theme — hardcoding a guessed alias silently does nothing.
  • Transition exceptions surface as 500s. Spatie throws TransitionNotFound / InvalidTransition; catch them in the action and convert to a Notification::make()->danger() rather than letting the Livewire request explode.
  • Extension points worth knowing: StateColumn and the action components accept closures for color(), icon(), and label(), so anything the config can't express you can override per-column without forking. For a genuinely custom look, publish views and swap the badge view rather than fighting the resolver chain.
  • Upgrade note: v2.2.0 is additive — no breaking changes from v2.1.x. If you had patched or forked the package purely to change hardcoded icons, drop that patch and use FilamentIcon::register() instead; the fork will break on future upgrades for no benefit.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin