laravel-status-transition maintained by rizalsaja
Description
A simple and flexible trait to add state machine behaviour to Laravel Eloquent models, with transition validation and automatic history tracking.
Author
Last update
2026/05/05 05:55
(dev-main)
License
Downloads
1
Tags
Laravel Status Transition
A simple and flexible trait to add state machine behaviour to Laravel Eloquent models, with transition validation and automatic history tracking.
Features
- Attach status state machine to any Eloquent model via a single trait
- Define allowed statuses and enforce valid transition paths
- Automatic status history recording with reason and actor tracking
- Polymorphic history — one
status_historiestable for all models - Query scopes for filtering by status
- Configurable: disable history recording globally via config
- Auto-discovery support — no manual provider registration needed
Requirements
| Package version | Laravel | PHP |
|---|---|---|
| 1.x | 10, 11, 12 | 8.1+ |
Installation
Install via Composer:
composer require rizalsaja/laravel-status-transition
The service provider is auto-discovered. No manual registration needed.
Publish the config file:
php artisan vendor:publish --tag=laravel-status-transition-config
Publish and run the migrations:
php artisan vendor:publish --tag=laravel-status-transition-migrations
php artisan migrate
Usage
1. Add the trait to your model
use Rizalsaja\LaravelStatusTransition\Traits\HasStatus;
class Order extends Model
{
use HasStatus;
/**
* All valid statuses for this model.
*/
protected $statuses = [
'pending',
'processing',
'shipped',
'delivered',
'cancelled',
];
/**
* Allowed transition map.
* Omit this property to allow all transitions freely.
*/
protected $transitions = [
'pending' => ['processing', 'cancelled'],
'processing' => ['shipped', 'cancelled'],
'shipped' => ['delivered'],
'delivered' => [],
'cancelled' => [],
];
}
Make sure your model's table has a status column:
$table->string('status')->default('pending');
2. Transition to a new status
$order = Order::create(['title' => 'New Order']);
// Simple transition
$order->transitionTo('processing');
// With a reason
$order->transitionTo('cancelled', reason: 'Customer requested cancellation');
3. Check current status
$order->getCurrentStatus(); // 'processing'
$order->isStatus('processing'); // true
$order->isNotStatus('shipped'); // true
$order->canTransitionTo('shipped'); // true
$order->availableTransitions(); // ['shipped', 'cancelled']
4. Query by status
Order::whereStatus('pending')->get();
Order::whereNotStatus('cancelled')->get();
Order::whereStatusIn(['pending', 'processing'])->get();
5. Access history
// All history records (ordered by latest inserted)
$order->statusHistory;
// Most recent record only
$order->latestStatus;
// History fields
$history->from; // 'pending'
$history->to; // 'processing'
$history->reason; // 'Payment confirmed'
$history->changed_by; // user id (nullable)
$history->created_at;
6. Resolve back to the model
$history = $order->statusHistory->first();
$history->statusable; // returns the Order instance
Configuration
After publishing, edit config/laravel-status-transition.php:
return [
/*
* Default statuses if the model does not define its own $statuses property.
*/
'default_statuses' => ['active', 'inactive'],
/*
* Set to false to disable status history recording entirely.
*/
'record_history' => true,
];
Customisation
Custom status column
// default: 'status'
protected $statusColumn = 'state';
Custom initial status
// default: first item in $statuses
protected $initialStatus = 'draft';
Allow all transitions freely
Omit $transitions from your model. Without it, any status can transition to any other status defined in $statuses.
Error Handling
use Rizalsaja\LaravelStatusTransition\Exceptions\InvalidStatusTransitionException;
try {
$order->transitionTo('shipped'); // invalid from 'pending'
} catch (InvalidStatusTransitionException $e) {
// "Cannot transition from [pending] to [shipped]. Allowed transitions: [processing, cancelled]."
report($e);
}
try {
$order->transitionTo('unknown');
} catch (\InvalidArgumentException $e) {
// "Status [unknown] is not a valid status."
report($e);
}
Testing
vendor/bin/phpunit --testdox
Changelog
Please see CHANGELOG.md for recent changes.
Contributing
Please see CONTRIBUTING.md for details.
License
The MIT License (MIT). Please see LICENSE.md for more information.