atannex/workflow
Atannex Workflow brings state-machine style workflow management to Laravel Eloquent models. Define states and transitions, attach guards and actions, and move records through approval or processing pipelines with events and a clean, testable API.
Start by confirming the runtime matrix before adding the dependency: PHP 8.3+ and Laravel 11 or 12 only. On older Laravel/PHP versions this package is not an option without an upgrade plan.
composer require atannex/workflow
Then follow the README setup flow closely. For a package like this, the first places to look are:
A sensible first use case is a single linear workflow on a low-risk model, for example:
draft → submitted → approved → publishedsubmit (required fields present, user has permission)approve (approver is not the submitter)Do not start with multi-person approvals, escalation, and timers simultaneously. Get one state machine working end-to-end first, including tests, then layer on approvals and time-based behavior.
Use the package’s workflow definition mechanism to declare states and transitions, but keep guards thin. A guard should answer “is this transition allowed right now?” — not send mail, call external APIs, or mutate unrelated models. For heavier work, use model/domain events or listeners after a transition succeeds.
A practical split:
draft, in_review, approved, rejected, expired).submit, approve, reject, escalate, expire).Day-to-day usage should look like application commands:
DB::transaction(function () use ($document, $user) {
$document->transitionTo('submitted', actor: $user);
});
Confirm the exact method names in the README. The important pattern is: one transition call per business action, inside a transaction, with the acting user explicitly passed where supported. This keeps the audit trail attributable and makes retries safer.
Multi-person approvals fit workflows where a single actor must not be able to complete a step alone. Model approval steps explicitly:
Keep Laravel policies for “can this user interact with this record at all” and use workflow guards/approvals for “can this user complete this specific transition.” Mixing the two leads to duplicated and inconsistent permission logic.
The task inbox is the feature end users will notice first. Build a dedicated query layer around it:
If the package exposes query scopes or relation methods, wrap them in your own repository/service so the rest of the application does not depend directly on package internals. This also gives you one place to add tenant scoping, soft-delete filtering, and eager loading.
Deadlines, escalation, timers, and automatic transitions imply background processing. Treat these as production infrastructure, not optional extras:
A useful pattern is to keep automatic transitions declarative where possible and use queued jobs only for external side effects. Avoid hiding critical state changes inside jobs that may be delayed indefinitely.
The audit trail is not only for compliance. Use it for:
Expose a read-only timeline in your admin UI rather than requiring developers to query package tables directly. That keeps the presentation stable if internal table names change.
Good integration points for a workflow engine:
A useful frontend contract is: for a given record and user, return current state, available transitions, required approvals, and pending tasks. Build that as an application endpoint rather than letting the frontend infer workflow rules.
PHP 8.3+ and Laravel 11/12 are non-negotiable. Check CI matrices, Docker images, and shared package constraints before adopting. A v1.0.0 release is stable by declaration, but a new package still deserves a spike branch and a rollback plan.
Two users may attempt transitions at the same time. Use database transactions and, where the package supports it, optimistic locking or row locks. Test the race explicitly:
If the package does not provide locking guarantees, add them at the application layer and document the boundary.
Guards are often evaluated multiple times — during UI rendering, API responses, and the actual transition. Any guard that writes data, sends notifications, or calls external services will produce duplicate or premature side effects. Keep guards pure and cheap.
Deadlines and escalation are deceptively hard. Decide and document:
Use Carbon::setTestNow() or Laravel’s time-travel helpers in tests, and write explicit tests for boundary conditions.
Automatic transitions and escalation rules can chain. A rejected approval that automatically returns to review, which immediately re-escalates, can create noisy loops. Add:
A full audit trail grows quickly. Plan for retention, archival, and indexing from the start. At minimum, index by workflow subject, actor, transition, and timestamp. If audit entries are written synchronously in the same transaction, watch for write amplification on high-volume models.
If you add workflow state to an existing table, every row needs an initial state. Do not rely on a nullable column and implicit defaults scattered through code. Write a migration/backfill that sets a known initial state, and make the application treat unknown states as an error rather than silently allowing transitions.
A task inbox must respect both workflow assignment and application authorization. A user may be the assigned approver but no longer have access to the record due to team changes, soft deletes, or tenant moves. Always join inbox queries back to your authorization layer, and test reassignment and revocation scenarios.
Before building much on top of the package, locate and document:
If definitions are cached, a deploy that changes states or transitions needs an explicit cache-clear step. Missing this is a common source of “works locally, broken in production” workflow bugs.
Write tests at three levels:
Use RefreshDatabase, factories for workflow subjects, and time travel for deadlines. For inbox features, test as different users and teams, not only as an administrator.
The package covers a lot: states, guards, approvals, inboxes, deadlines, escalation, timers, automatic transitions, and audit. Adopt it incrementally. A single model with a simple state machine and audit trail is a good production pilot. Add multi-person approvals and escalation only after the basics are observable, tested, and understood by the team operating them.
How can I help you explore Laravel packages today?