spatie/laravel-model-states
Add state-machine behaviour to Eloquent models: each state is its own class, with serialization handled automatically and state transitions made easy.
allowTransition() declaration, which turns "status is a string we all hope nobody updates directly" into a reviewable rule set in code.if ($order->status === 'paid') guards across controllers, jobs, and admin panels. That build cost is small at first and grows with every new state. Adopting this package buys the transition guard, serialization, default-state assignment, and transition events for roughly an afternoon of integration on a package with a very narrow surface area.color(), label(), allowed next actions, notification routing) or are just labels. If behavior differs per state, this package pays for itself immediately; if states are purely cosmetic, a plain enum cast is enough.StateChanged events (now including the field name) give you a clean hook to persist a transition history table and emit analytics/audit records. If a roadmap item is "show customers a full status timeline" or "prove SOC2/compliance-grade change logs," this is the natural seam to build on — and it should be scoped as part of the same epic, since the package doesn't ship history itself.transitionableStateInstances() lets a controller or Inertia/Livewire payload emit exactly the buttons a user is allowed to press, so admin tooling stops re-implementing state rules in Blade templates. That's a direct reduction in "the UI let me do an illegal thing" bug tickets.Consider adopting when:
Look elsewhere when:
For executives (one paragraph): We have several core objects — payments, orders, subscriptions, onboarding applications — whose lifecycle rules currently live in scattered conditionals across controllers and admin screens. That means invalid transitions reach production, and answering "how did this record get here?" requires log archaeology. This is a mature, MIT-licensed Laravel package from Spatie (~1.3k stars, actively maintained with releases tracking current Laravel and PHP versions) that makes each state and each legal transition explicit in code. The investment is roughly one engineer-day to integrate on the first model plus a small, ongoing convention we hold ourselves to. The return is fewer money-and-entitlement bugs, an audit-ready transition log we can show customers and auditors, and faster delivery on every future "add a new status" request, because adding a state becomes a reviewed diff rather than a scavenger hunt. There's no infrastructure, no new service, and no lock-in — it's a Composer dependency we can remove.
For engineering (the substance):
'state' => PaymentState::class), with configuration — default state, allowed transitions, custom transition classes — declared on the abstract state class rather than the model. That colocates the rules with the behavior and removes the 'status' field argument threading that older versions required.color(), label(), notificationChannel(), all polymorphic via the abstract base class, no switch statements.canTransitionTo() / transitionTo(), equals() semantics, custom transition classes implementing their own canTransition, and transitionableStateInstances() for feeding UI action lists.StateChanged events (with the field name as of 2.12) give a single, reliable seam for history tables, audit logs, and analytics — build that listener in the same PR as the first stateful model.ValidationRule interface changes), so inbound API requests can constrain to permitted transitions.How can I help you explore Laravel packages today?