Supports: Laravel 11, 12 & 13+ • PHP 8.2+ • Redis • Memcached • Database
Application-level workflow orchestration for Laravel. Model business processes, manage state transitions, and track workflow history using cache or database storage.
Manage workflows with a clean, expressive API using cache or persistent database storage, attach them directly to Eloquent models, protect routes with declarative middleware, and extend the package with custom storage drivers.
Workflow Definition Defines the workflow graph (for example,
OrderWorkflow).Workflow Instance Tracks a specific entity moving through that graph (for example,
Order #1542).
Laravel Workflow manages workflow instances with validated state transitions, audit history, rollback support, and interchangeable storage drivers.
Created
│
▼
In Progress
│
┌─┴────────────┐
▼ ▼
Approved Rejected
│
▼
Completed
Workflow::for($order, new OrderWorkflow())
->block('payment_processed', function () {
// Capture payment...
// Send confirmation email...
});
$workflow->rollbackTo('previous_step').Workflow::for($order, new OrderWorkflow())->using('database')->transition('shipped').HasWorkflows trait to any model for scoped workflow management.workflow:step_name,WorkflowClass with automatic HTTP 409 enforcement.WorkflowStateInfo and WorkflowHistoryItem Data Transfer Objects.Prunable trait integration ensures completed or archived workflow instances never clutter your database.A simple status column works well for straightforward applications.
Laravel Workflow is designed for applications where the process itself is part of the business logic.
| Feature | Status Column | Laravel Workflow |
|---|---|---|
| Primary Purpose | Simple column enum/status | Multi-step process orchestration & audit trails |
| Fluent Builder API | ❌ Manual | ✅ Expressive & Clean |
| First-Class Eloquent Integration | ❌ | ✅ Native (HasWorkflows) |
| Storage Architecture | ❌ DB Column Only | ✅ Cache & Database |
| Transition Validation | ❌ | ✅ |
| Chronological Audit Logs | ❌ Manual Table | ✅ Built-in (history()) |
| Historical Transition Data | ❌ | ✅ |
| Rollback to Previous Steps | ❌ | ✅ Built-in (rollbackTo()) |
| Auto-Rollback Route Middleware | ❌ | ✅ Built-in (RequireWorkflowStep) |
| Immutable DTOs | ❌ | ✅ Strict (CarbonImmutable) |
| Automatic Database Pruning | N/A | ✅ Built-in (Prunable) |
Ready to get started? Install the package with Composer:
composer require zaber-dev/laravel-workflow
Publish the configuration and database migrations:
php artisan vendor:publish --provider="ZaberDev\Workflow\WorkflowServiceProvider"
Run migrations if you intend to use the database driver:
php artisan migrate
The configuration file lets you define the default storage driver, driver parameters, and event dispatching behaviors:
return [
/*
|--------------------------------------------------------------------------
| Default Workflow Driver
|--------------------------------------------------------------------------
|
| Supported drivers: "cache", "database"
|
*/
'default' => env('WORKFLOW_DRIVER', 'cache'),
'drivers' => [
'cache' => [
'driver' => 'cache',
'store' => env('WORKFLOW_CACHE_STORE', null),
'prefix' => 'workflows:',
],
'database' => [
'driver' => 'database',
'table' => 'workflow_instances',
],
],
'events' => [
'dispatch' => true,
],
];
Implement WorkflowDefinitionContract to define your steps, valid transitions, and starting point:
namespace App\Workflows;
use ZaberDev\Workflow\Contracts\WorkflowDefinitionContract;
class OrderProcessingWorkflow implements WorkflowDefinitionContract
{
public function name(): string
{
return 'order_processing';
}
public function initialStep(): string
{
return 'created';
}
public function steps(): array
{
return ['created', 'inventory_reserved', 'payment_processed', 'shipped', 'cancelled'];
}
public function transitions(): array
{
return [
'created' => ['inventory_reserved', 'cancelled'],
'inventory_reserved' => ['payment_processed', 'cancelled'],
'payment_processed' => ['shipped', 'cancelled'],
'shipped' => [],
'cancelled' => [],
];
}
}
The Workflow facade provides an expressive builder interface for inspecting, transitioning, and rolling back workflow instances.
use ZaberDev\Workflow\Facades\Workflow;
$instance = Workflow::for($order, new OrderProcessingWorkflow());
if ($instance->can('inventory_reserved')) {
$instance->transition('inventory_reserved', [
'reserved_at' => now()->toIso8601String(),
'warehouse' => 'US_WEST_1'
]);
}
// Check current status
echo $instance->current(); // "inventory_reserved"
block)For operations vulnerable to partial failure across external APIs, use the block() helper. block() transitions to your target step, executes your callback, and automatically rolls back (rollbackTo) if the closure throws an exception:
Workflow::for($order, new OrderProcessingWorkflow())->block('payment_processed', function () use ($order) {
$this->stripeService->charge($order);
}, payload: ['gateway' => 'stripe']);
// Inspect chronological step history
foreach ($instance->history() as $item) {
echo "Step: " . $item->step . " at " . $item->transitionedAt->toDateTimeString();
}
// Rollback to a previous step
$instance->rollbackTo('created', 'Payment failed, returning order to created.');
HasWorkflows)Add the HasWorkflows trait to any Eloquent model (such as Order, Document, or User) to manage workflow instances directly off the entity:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use ZaberDev\Workflow\HasWorkflows;
class Order extends Model
{
use HasWorkflows;
}
You can now interact directly with your model instance:
$order = Order::find(1);
// Transition this order to "payment_processed"
$order->workflow(new OrderProcessingWorkflow())->transition('payment_processed');
// Check current step
if ($order->workflow(new OrderProcessingWorkflow())->current() === 'shipped') {
// Order already shipped
}
When using the database driver, HasWorkflows also exposes a workflowInstances() polymorphic relationship, allowing direct querying and bulk management:
// Get all database workflow instances for this order
$instances = $order->workflowInstances()->get();
Protect routes declaratively without writing boilerplate checks in your controllers using the RequireWorkflowStep middleware:
use Illuminate\Support\Facades\Route;
// Enforce that $order is currently in the "payment_processed" step before allowing shipment dispatch
Route::post('/orders/{order}/ship', [ShipmentController::class, 'ship'])
->middleware('workflow:payment_processed,App\Workflows\OrderProcessingWorkflow');
How the Middleware Works
using & driver)By default, the package uses the storage driver defined in config/workflows.php. You can switch storage drivers on the fly per request or action:
// Store high-speed ephemeral user wizard workflows in cache
Workflow::for($user, new WizardWorkflow())->using('cache')->transition('step_2');
// Store critical audit-trailed order workflows inside SQL database
Workflow::for($order, new OrderProcessingWorkflow())->using('database')->transition('shipped');
You can extend the WorkflowManager with your own storage drivers (e.g., DynamoDB, MongoDB) in your AppServiceProvider:
use ZaberDev\Workflow\Contracts\WorkflowDriverContract;
use ZaberDev\Workflow\Facades\Workflow;
public function boot(): void
{
Workflow::extend('dynamodb', function ($app) {
return new MyDynamoDbWorkflowDriver($app['config']['workflows.drivers.dynamodb']);
});
}
Prunable)When using the database driver, completed or cancelled instances can be marked for pruning via Laravel's Prunable trait on the ZaberDev\Workflow\Models\WorkflowInstanceModel model.
To clean up old records automatically, schedule Laravel's model:prune command in your console.php or Kernel.php:
use Illuminate\Support\Facades\Schedule;
use ZaberDev\Workflow\Models\WorkflowInstanceModel;
Schedule::command('model:prune', ['--model' => WorkflowInstanceModel::class])->daily();
Whenever a workflow transitions or rolls back, the package dispatches strongly typed events if enabled (workflows.events.dispatch = true):
ZaberDev\Workflow\Events\WorkflowTransitioned: Dispatched when transition() succeeds ($key, $oldStep, $newStep, $payload, $info).ZaberDev\Workflow\Events\WorkflowRolledBack: Dispatched when rollbackTo() succeeds ($key, $targetStep, $reason, $info).You can listen to these in your EventServiceProvider for notification emails, analytics tracking, or webhook triggers.
This package is part of the ZaberDev Laravel Ecosystem (Laravel Productivity Toolkit) — a cohesive suite of high-level application primitives engineered for concurrency, state management, and resource allocation.
Explore the complete directory of packages, detailed use cases, and documentation in our Ecosystem Index Hub.
Run the comprehensive PHPUnit test suite locally:
composer test
Thank you for considering contributing! Please ensure any pull requests include thorough PHPUnit tests covering unit, feature, and driver integration scenarios.
The MIT License (MIT). Please see LICENSE.md for more information.
How can I help you explore Laravel packages today?