byrcsc/laravel-approval · 1.x
Introduction.
Multi-stage approval workflows for Eloquent models, with database-backed workflows, attribute drafts, delegation, SLAs, and a verifiable action history.
Laravel Approval turns "this needs sign-off before it counts" into an engine: ordered approval stages held in the database, approver assignments snapshotted at submission, proposed attribute changes held until the last approval, and an append-only action history you can verify.
The package provides the approval engine. Your application keeps ownership of its UI, users, roles, and organizational rules.
| Requirement | Supported versions |
|---|---|
| PHP | 8.3, 8.4 |
| Laravel | 12.x, 13.x |
The package follows semantic versioning: upgrading within 1.x is safe.
Source and issues live at
github.com/byrcsc/laravel-approval.
What the package approves
An approval request is one execution of an approval workflow for one already-persisted record. A workflow is made of ordered approval stages, each holding one approval assignment per approver. An approver records an approval decision: approve, reject, or return for revision.
The package controls approval state and history. It does not control your model's lifecycle:
- Record-level approval means a request is associated with a record that has already been saved. Approving it changes the request's state and history and nothing about the record. The package does not hide, publish, activate, revert, or delete the record, and it does not gate its creation.
- Attribute drafts are the built-in hold-until-approved mechanism. Proposed attribute values are submitted with the request and held there rather than written to the record; they are applied only after the final approval.
If you need creation, publication, or visibility gating, keep that in your application, driven by the package's events and request state.
What is included
- Sequential, database-driven workflows with ordered stages and
1-of-N,N-of-M, or unanimous approval requirements. - A rejection policy configured separately from the approval threshold: reject when the required approvals become unreachable, or make any rejection a veto.
- Concrete approvers, plus approver resolvers for roles, managers, teams, and organization-specific rules.
- Snapshotted workflows, conditional stages, and version-controlled workflow definitions with safe preview and sync commands.
- Several workflows per model, selected by condition and priority, with the queries a workflow picker is built from.
- Attribute drafts, cast-aware stale checks, explicit conflict resolution, and revise-and-resubmit flows.
- Eligibility read models, a Laravel policy, bulk decisions, delegation, reassignment, and administrative approver resynchronization.
- Optional automatic submission after creation, and guard mode for Eloquent writes touching attributes held in a pending draft.
- SLA deadlines, reminders, configurable escalation strategies, stranded-stage detection, and operational status commands.
- Notifications with publishable wording and mail views, lifecycle events, inbox
and progress queries, model scopes, timelines, factories, and an
Approval::fake()test helper. - An append-only, hash-chained action history with attachment evidence and
verification through
approval:verify.
A first approval
Add the Approvable concern to the model that needs sign-off, create a
workflow with at least one stage, and submit:
use ByRcsc\LaravelApproval\Concerns\Approvable;
use ByRcsc\LaravelApproval\Facades\Approval;
class PurchaseOrder extends Model
{
use Approvable;
}
$request = $order->submitForApproval('purchase-order', ['amount' => 10_000]);
Approval::approve($request, $financeUser);The new amount is not written to the order until the last required approval
is recorded. Until then it is held on the request, readable through
$order->pendingDraft()->new.
Design boundaries
Four decisions shape almost everything else in the package.
One exact identity per approver. An approver entry names one actor. A
Team, Role, or Position model is never expanded into its members;
assigning a team assigns the team record itself. To assign the members, use an
approver resolver.
Snapshots, not live lookups. Submission copies the workflow, its stages, their conditions, and the resolved assignments onto the request. A later workflow or org-chart change never rewrites in-flight work.
One unresolved request per record. Pending and conflicted both count as unresolved. A second submission for the same record throws until the first one closes.
One command path. Every mutation goes through the Approval facade. The
model carries two aliases only: submitForApproval(), for the one operation
that starts on the model, and withoutApprovalGuard(), because guard mode is
itself a model-level feature.
Where to go next
Continue with installation and setup, then follow the quick start to build a two-stage workflow end to end. The remaining sections document workflows, resolvers, drafts, delegation, escalation, authorization, events, audit verification, and every Artisan command the package ships.