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

Laravel Model States Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

  • Modeling multi-step lifecycles as first-class domain objects. Orders, payments, invoices, subscriptions, KYC/onboarding, ticket escalation, content moderation, and fulfillment all have a small set of named states plus rules about which moves are legal. This package makes each state a class and each legal move an explicit allowTransition() declaration, which turns "status is a string we all hope nobody updates directly" into a reviewable rule set in code.
  • Build vs. buy on workflow logic. The alternative is hand-rolling a status enum plus scattered 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.
  • Choosing between "status flag" and "state machine" architecture. The decision hinges on whether states carry behavior (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.
  • Guarding roadmap bets on auditability and compliance. 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.
  • Driving UI from the domain instead of duplicating rules. 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.
  • Sequencing work. Practical phasing: (1) introduce states on the highest-pain model, (2) add the history listener, (3) expose allowed transitions to the frontend, (4) expand to adjacent models. This keeps the dependency confined to one bounded context at a time rather than becoming an enterprise-wide state framework.
  • Deciding against a heavier workflow engine. If the lifecycle is genuinely linear with guard clauses, this is the cheaper choice versus Temporal/Camunda/BPMN-style tooling that brings infrastructure, ops burden, and a much larger conceptual footprint than the problem warrants.

When to Consider This Package

Consider adopting when:

  • The model has 3+ states with distinct behavior per state, not just a label.
  • Illegal transitions are a real business risk (money movement, entitlements, compliance) rather than a cosmetic concern.
  • You're on a supported Laravel 8+ / PHP 7.4+ stack, ideally a current LTS-adjacent version — the changelog shows active support through Laravel 13 and PHP 8.5 fixes, so staying current is well-served.
  • The workflow is developer-defined and code-reviewed. Teams that want product managers to rewire states from an admin UI at runtime should look elsewhere.
  • You want a small, idiomatic-Laravel dependency that behaves like a native model cast rather than a framework inside your framework.
  • You're comfortable owning the surrounding pieces: history persistence, notification side effects, and a convention that nobody writes raw state values to the column.

Look elsewhere when:

  • States are 2–3 simple flags with no divergent behavior — a native enum cast plus a validation rule is less machinery and less to explain to new hires.
  • You need parallel states, timers, SLA escalations, compensation/saga semantics, or cross-service orchestration — that's a workflow engine, not a model concern.
  • You need a business-configurable state machine owned by operations, not engineering.
  • Your read model is primarily reporting/analytics and you'd rather encode lifecycle as DB constraints and materialized views than PHP classes.
  • You have multiple interacting models with coordinated transitions in one transaction — the package is per-model and won't give you distributed consistency.
  • Team-fit risk: one class per state means dozens of tiny files. If the team is small, junior-heavy, or resistant to class explosion, the maintenance overhead can outweigh the guardrails. Similarly, the 2.0 release was a breaking redesign of how states bind to fields — pin the version and read upgrade notes before jumping majors.
  • Vendor concentration: this is one of several Spatie packages you'd likely already depend on. That's a reasonable risk, but note the "dependents: 0" signal here is unreliable as a popularity proxy — validate adoption by checking Packagist download counts and your own team's prior use rather than trusting the metadata.

How to Pitch It (Stakeholders)

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):

  • States become classes cast onto the model ('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.
  • Per-state behavior lives where it belongs: color(), label(), notificationChannel(), all polymorphic via the abstract base class, no switch statements.
  • Transition guarding is first-class: 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.
  • Validation integration exists (the package ships a validation rule and tracks Laravel's ValidationRule interface changes), so inbound API requests can constrain to permitted transitions.
  • Risks we should own explicitly: enforce a "never write the raw column" convention via code review and a lint/static-analysis rule; confirm the DB column can hold the morph values and index accordingly; pin the major version given the 2.0 breaking redesign; and budget for class-per-state file count on models with many states.
  • Verdict: low integration cost, low blast radius, high leverage on lifecycle-heavy domains. Start with the single highest-risk model, prove the pattern, then expand.
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.
alphabalex/messaging-made-easy
rawphp/warp
shootkiran/merobug
narakode/fineauth
numencode/wn-syncops-plugin
ibnnajjaar/mib-global-pay
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator