Download the PHP package maestrodimateo/laravel-workflow without Composer
On this page you can find all versions of the php package maestrodimateo/laravel-workflow. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download maestrodimateo/laravel-workflow
More information about maestrodimateo/laravel-workflow
Files in maestrodimateo/laravel-workflow
Package laravel-workflow
Short Description Workflow engine for Laravel applications
License MIT
Informations about the package laravel-workflow
Laravel Workflow
A visual, configurable workflow engine for Laravel. Define circuits (workflow definitions), baskets (steps), and transitions — then move any Eloquent model through them with a clean Facade API.
Comes with a built-in visual admin interface to design your workflows by drag-and-drop.
Features
- Visual workflow designer — drag-and-drop baskets, draw transitions, configure actions & conditions
- Facade & helper —
Workflow::for($model)->transition($basketId)orworkflow($model)->transition($basketId) - Multi-circuit — a model can belong to multiple workflows, scoped with
in($circuit) - Resource locking — prevent concurrent access with
lock()/unlock() - Role-based access — define allowed roles per circuit and per basket
- Transition actions — attach actions (email, webhook, log, require documents, custom) to transitions
- Conditional transitions — gate a transition on the model's state; enforced server-side and surfaced to your UI
- Duration tracking — automatic timing between steps with human-readable formatting
- Full history — every transition is logged with who, when, how long, and why
- Message templates — WYSIWYG editor with variable interpolation
- Export / Import — share workflows as JSON files + PNG image export
- Dark mode — the admin UI supports light and dark themes
- No CDN, no npm — the admin UI ships vendored assets (compiled Tailwind + Alpine + Quill), served same-origin
Requirements
- PHP 8.3+
- Laravel 12 or 13
Installation
Publish the config and migrations:
Optionally publish the views to customize the admin UI:
Quick Start
1. Add the trait to your model
When an Invoice is created, it's automatically placed in the DRAFT basket of the circuit targeting it.
2. Open the visual designer
Navigate to /workflow/admin in your browser. From there you can:
- Create a circuit (workflow) targeting your model
- Add baskets (steps) with colors and roles
- Draw transitions (links) between baskets by dragging from one to another
- Configure actions on each transition (send email, call webhook, etc.)
3. Transition models in your code
The workflow() helper is also available:
Facade API Reference
All methods are available via Workflow:: or workflow()->.
Model-bound methods
These methods require Workflow::for($model) first:
| Method | Returns | Description |
|---|---|---|
in($circuit) |
WorkflowManager |
Scope to a specific circuit (required for multi-circuit) |
currentStatus() |
?Basket |
Current basket (step) of the model |
nextBaskets() |
Collection |
Available baskets to transition to |
availableTransitions() |
array |
Next baskets each with open (bool) + blockedBy (reasons) — evaluates conditions |
transition($id, $comment) |
bool |
Move the model to the next basket |
history() |
Collection |
Full transition history with durations |
totalDuration() |
int |
Total processing time in seconds |
durationInStatus($status) |
int |
Time spent in a specific status (seconds) |
allStatuses() |
array |
Current basket per circuit |
circuits() |
Collection |
All circuits the model belongs to |
lock($minutes) |
WorkflowLock |
Lock the model for exclusive access |
unlock($force) |
void |
Release the lock |
isLocked() |
bool |
Check if locked |
isLockedByMe() |
bool |
Check if locked by the current user |
lockedBy() |
?string |
User ID holding the lock |
lockExpiration() |
?Carbon |
When the lock expires |
requiredDocuments($basketId) |
array |
Documents required for a transition |
requirements() |
array |
All requirements for all next transitions |
Static methods
These methods don't require for():
| Method | Returns | Description |
|---|---|---|
importFromJson($path) |
Circuit |
Import a circuit from an exported JSON file (for seeders/commands) |
registerAction($class) |
void |
Register a custom transition action |
getRegisteredActions() |
array |
List all registered action classes |
registerCondition($class) |
void |
Register a custom transition condition |
getRegisteredConditions() |
array |
List all registered condition classes |
Role-based queries
These methods don't require for():
Eloquent scopes are also available directly:
Duration Tracking
Every transition automatically records the time spent in the previous step:
Human-readable formats: 45s, 12min, 2h 35min, 3j 4h.
Multi-Circuit Support
A model can belong to multiple circuits simultaneously. Use in() to scope operations:
When a model is created, it's automatically attached to the DRAFT basket of every circuit targeting its class.
Resource Locking
Prevent multiple operators from working on the same model simultaneously:
Query scopes
Handling lock exceptions
Configure the default lock duration in .env:
Transition Actions
Actions are executed automatically when a specific transition occurs. They are configured visually in the admin UI.
Built-in actions
| Action | Key | Config | Runs |
|---|---|---|---|
| Send email | send_email |
Select a message from the circuit | Queue (after commit) |
| Webhook | webhook |
URL to POST to | Queue (after commit) |
| Log | log |
Optional message | After commit |
| Require documents | require_document |
List of documents (type + label) | In transaction |
Transaction behavior
Every transition() is wrapped in a single DB transaction (model move, history insert, lock release). Each action runs in one of three modes depending on which marker interface it implements:
- In transaction (default) — the action runs inside the transition transaction. A thrown exception rolls back the entire transition. Use this for validations (e.g.,
require_document) or DB writes that must be atomic with the transition. - After commit (
AfterCommitAction) — the action runs inline once the transition has been committed to the database. Use this for fast non-rollbackable side effects (logging, in-memory cache invalidation) where queueing would be overkill. - Queued (
QueueableAction) — the action is wrapped in a job and dispatched after commit, executed by a queue worker. Use this for slow or external side effects (HTTP calls, email, third-party APIs) so the request returns immediately and transient failures can be retried by the worker.
SendEmailAction and WebhookAction implement QueueableAction. LogTransitionAction implements AfterCommitAction. RequireDocumentAction runs inline — its throw must be able to abort the transition.
Custom actions
Generate a new action with artisan:
This creates app/Workflow/Actions/GeneratePdfAction.php:
Register it in your AppServiceProvider::boot():
The action immediately appears in the admin UI's "Add action" menu on any transition.
Opting an action out of the transaction
If your custom action produces an external side effect that cannot be rolled back (HTTP call, email, push notification, third-party SDK), add the AfterCommitAction marker so it only runs once the transition is safely committed:
Validation-style actions (those that may throw to abort the transition) should NOT implement AfterCommitAction — their exception needs to roll back the transition, which is only possible from inside the transaction.
Running an action on a queue
For slow side effects (HTTP calls, email, third-party APIs), implement QueueableAction instead. The action is wrapped in a job and dispatched after commit, so the request returns immediately and the worker picks it up:
What you get for free:
- Race-free dispatch — the job is sent inside
DB::afterCommit(), so the worker can never read the transition's rows before they're visible. - Fresh state on the worker — the subject, source and target baskets are serialized by reference (
SerializesModels) and re-fetched from the DB when the job runs. - Automatic retries — Laravel's queue worker handles retries, backoff, and failed-jobs storage like any other job.
Override the queue and connection globally via .env:
With QUEUE_CONNECTION=sync (the Laravel default), queueable actions run inline on the request — useful for local development without a running worker. Switching to redis, database, or sqs is enough to move them off the request lifecycle; no code change required.
A
QueueableActionalready runs after commit — you don't need to also implementAfterCommitAction.
Transition Conditions
Gate a transition on the model's own state. A condition is evaluated in two places with the same result: it blocks the transition server-side (with a rollback) and it tells your own UI whether a transition is open.
Built-in: attribute condition (no code)
In the designer, open a transition, add a Condition → Model attribute, then pick a field, an operator and a value:
| Operator | Meaning |
|---|---|
= != |
equals / not equals |
< <= > >= |
comparisons |
in not_in |
value in a comma-separated list |
empty not_empty |
attribute is blank / present |
contains |
string contains |
Example: only allow Draft → Approved when amount <= 1000.
Custom conditions (code)
For anything the attribute editor can't express, implement TransitionCondition. passes() MUST be side-effect free (it also runs when building the UI):
Register it in config/workflow.php (the built-in attribute condition is always available):
Enforcement & the consumer UI
- Server —
transition()throwsTransitionConditionException(and rolls back) when a condition fails;$e->reasonsholds the messages. - Your UI —
availableTransitions()returns each next basket with whether it is open and why not, so you can hide or disable blocked steps.
Conditions must be read-only. The bulk
transitionMany()admin path does not evaluate them (same as actions).
Events
A TransitionEvent is fired after every transition. Add your own listeners:
What happens during a transition
Message Templates
Messages are created at the circuit level and can be used in transition actions (e.g., send_email).
The WYSIWYG editor supports variable interpolation:
Built-in variables
| Variable | Description |
|---|---|
{{ model_id }} |
Model identifier |
{{ model_type }} |
Model class name |
{{ from_status }} / {{ from_name }} |
Source basket |
{{ to_status }} / {{ to_name }} |
Target basket |
{{ circuit_name }} |
Circuit name |
{{ date }} / {{ heure }} / {{ datetime }} |
Current date/time |
{{ user }} |
User performing the transition |
Custom variables
Export / Import
In the admin UI
- Export JSON — download the full circuit definition as a
.jsonfile - Export PNG — download a high-resolution image of the workflow diagram
- Import — select a
.jsonfile to recreate a circuit with all its configuration
Via API
Programmatic import (seeders, commands)
Import a previously exported JSON file directly from code — no HTTP request needed:
The method creates the full circuit (baskets, transitions, messages) inside a database transaction and returns the newly created Circuit instance with all relations loaded.
Throws \InvalidArgumentException if the file is missing or has an invalid format.
Configuration
Workflowable Trait
The Workflowable trait adds relations, methods, and scopes directly on your model instance. Everything below is available without using the Facade.
Relations
Methods
Scopes
Automatic Behavior
When a model is created, it's automatically attached to the DRAFT basket of every circuit targeting its class:
Admin Interface
The visual designer at /workflow/admin provides:
- Circuit management — create, edit, delete circuits with role assignment
- Drag-and-drop canvas — position baskets freely; the layout is saved per circuit, drag the background to pan, auto-layout button
- Visual linking — click output port, then click target basket to create transitions
- Transition config — click a link to add a label, actions and conditions
- Keyboard —
Delete/Backspaceremoves the selected basket - Message editor — WYSIWYG editor with variable interpolation
- Export / Import — JSON + PNG export, JSON import
- Zoom — scroll wheel + controls
- Dark mode — toggle between light and dark themes
- No build step — works out of the box (assets are vendored, see below)
Front-end assets
The designer's front-end libraries (a compiled, purged Tailwind stylesheet,
Alpine.js and Quill) are vendored under resources/dist/ and served
same-origin by the package at /workflow/assets/{file}. There is no CDN:
the UI works offline / air-gapped and under a strict Content-Security-Policy, and
nothing external is loaded (so Subresource Integrity is not applicable).
Only maintainers editing the Blade views need Node — regenerate the stylesheet after any markup change:
The committed resources/dist/app.css is the shipped artifact; consumers never
run a build.
Testing
Or with Pest directly:
License
MIT. See LICENSE for details.
Credits
All versions of laravel-workflow with dependencies
illuminate/database Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0
illuminate/events Version ^12.0|^13.0
symfony/html-sanitizer Version ^7.0