PHPackages                             rizalsaja/laravel-status-transition - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. rizalsaja/laravel-status-transition

ActiveLibrary[Database &amp; ORM](/categories/database)

rizalsaja/laravel-status-transition
===================================

A simple and flexible trait to add state machine behaviour to Laravel Eloquent models, with transition validation and automatic history tracking.

v1.0.1(1mo ago)11↓100%1[3 issues](https://github.com/RizalAnas00/laravel-status-transition/issues)[1 PRs](https://github.com/RizalAnas00/laravel-status-transition/pulls)MITPHPCI passing

Since May 1Pushed 2w ago1 watchersCompare

[ Source](https://github.com/RizalAnas00/laravel-status-transition)[ Packagist](https://packagist.org/packages/rizalsaja/laravel-status-transition)[ RSS](/packages/rizalsaja-laravel-status-transition/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (3)Dependencies (4)Versions (6)Used By (0)

Laravel Status Transition
=========================

[](#laravel-status-transition)

[![Tests](https://github.com/rizalsaja/laravel-status-transition/actions/workflows/tests.yml/badge.svg)](https://github.com/rizalsaja/laravel-status-transition/actions/workflows/tests.yml)[![Latest Version on Packagist](https://camo.githubusercontent.com/f4cf60e55a19ec3fb8145f33719803707727a4ed8a2b2c159726bbec48c3a25f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f72697a616c73616a612f6c61726176656c2d7374617475732d7472616e736974696f6e2e737667)](https://packagist.org/packages/rizalsaja/laravel-status-transition)[![Total Downloads](https://camo.githubusercontent.com/dfac6f8bc0df364186de0e8a2a709495820ca4ac5b018be13cb7aa10f396f9fd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f72697a616c73616a612f6c61726176656c2d7374617475732d7472616e736974696f6e2e737667)](https://packagist.org/packages/rizalsaja/laravel-status-transition)[![License](https://camo.githubusercontent.com/826a579e3b195c37d1e672c87f93fe4fb28c9c9b0b6cac4df645a06a6649fd74/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f72697a616c73616a612f6c61726176656c2d7374617475732d7472616e736974696f6e2e737667)](LICENSE.md)[![PHP Version](https://camo.githubusercontent.com/6c02c39be1f9d2be8de342733404b296b890dce267c7c09f0a665421364d613c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f72697a616c73616a612f6c61726176656c2d7374617475732d7472616e736974696f6e2e737667)](composer.json)

A simple and flexible trait to add state machine behaviour to Laravel Eloquent models, with transition validation and automatic history tracking.

Features
--------

[](#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_histories` table 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
------------

[](#requirements)

Package versionLaravelPHP1.x10, 11, 128.1+Installation
------------

[](#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
```

On-Progress Features
--------------------

[](#on-progress-features)

Warning

The following features are available on the [`1.1`](https://github.com/RizalAnas00/laravel-status-transition/tree/1.1) branch and are part of the upcoming **[v1.1.0](https://github.com/RizalAnas00/laravel-status-transition/milestone/1)** milestone. They are **not yet stable** and the API may change before the final release. Use at your own risk.

### Before and/or After Hooks for Status Transitions

[](#before-andor-after-hooks-for-status-transitions)

Attach `before` and/or `after` callbacks to specific transitions directly in your model's `$transitions` map.

```
protected $transitions = [
    'pending' => [
        'processing' => [
            'before' => 'validateStock',        // method name
            'after'  => 'sendProcessingEmail',  // method name
        ],
        'cancelled', // no hooks needed — plain string is fine
    ],
];

public function validateStock(): void
{
    // runs before the status is saved
}

public function sendProcessingEmail(): void
{
    // runs after the status is saved
}
```

Closures are also supported:

```
'cancelled' => [
    'after' => function ($model) {
        Log::info("Order {$model->id} was cancelled.");
    },
],
```

Usage
-----

[](#usage)

### 1. Add the trait to your model

[](#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

[](#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

[](#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

[](#4-query-by-status)

```
Order::whereStatus('pending')->get();
Order::whereNotStatus('cancelled')->get();
Order::whereStatusIn(['pending', 'processing'])->get();
```

### 5. Access history

[](#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

[](#6-resolve-back-to-the-model)

```
$history = $order->statusHistory->first();
$history->statusable; // returns the Order instance
```

Configuration
-------------

[](#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
-------------

[](#customisation)

### Custom status column

[](#custom-status-column)

```
// default: 'status'
protected $statusColumn = 'state';
```

### Custom initial status

[](#custom-initial-status)

```
// default: first item in $statuses
protected $initialStatus = 'draft';
```

### Allow all transitions freely

[](#allow-all-transitions-freely)

Omit `$transitions` from your model. Without it, any status can transition to any other status defined in `$statuses`.

Error Handling
--------------

[](#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
-------

[](#testing)

```
vendor/bin/phpunit --testdox
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG.md](CHANGELOG.md) for recent changes.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.

License
-------

[](#license)

The MIT License (MIT). Please see [LICENSE.md](LICENSE.md) for more information.

###  Health Score

33

—

LowBetter than 73% of packages

Maintenance75

Regular maintenance activity

Popularity5

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

4

Last Release

35d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a1a0296df068c26c9513ae0c0a00a2280649898062dab925a0a3ca4aa7a958ca?d=identicon)[rizalsaja](/maintainers/rizalsaja)

---

Top Contributors

[![RizalAnas00](https://avatars.githubusercontent.com/u/152856574?v=4)](https://github.com/RizalAnas00 "RizalAnas00 (16 commits)")

---

Tags

laraveleloquenttraittransitionstatusstate-machine

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/rizalsaja-laravel-status-transition/health.svg)

```
[![Health](https://phpackages.com/badges/rizalsaja-laravel-status-transition/health.svg)](https://phpackages.com/packages/rizalsaja-laravel-status-transition)
```

###  Alternatives

[kirschbaum-development/eloquent-power-joins

The Laravel magic applied to joins.

1.6k29.9M42](/packages/kirschbaum-development-eloquent-power-joins)[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k8.0M84](/packages/mongodb-laravel-mongodb)[spatie/laravel-sluggable

Generate slugs when saving Eloquent models

1.5k12.4M291](/packages/spatie-laravel-sluggable)[cybercog/laravel-love

Make Laravel Eloquent models reactable with any type of emotions in a minutes!

1.2k322.4k1](/packages/cybercog-laravel-love)[psalm/plugin-laravel

Psalm plugin for Laravel

3325.1M337](/packages/psalm-plugin-laravel)[watson/validating

Eloquent model validating trait.

9743.4M53](/packages/watson-validating)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
