byrcsc/laravel-approval · 1.x
Quick start.
Build a two-stage purchase-order workflow, submit a draft, approve it through both stages, and render an approver inbox.
This walkthrough builds a two-stage workflow, submits a purchase order with a proposed amount held until approval, and takes it through to completion.
1. Make the model approvable
use ByRcsc\LaravelApproval\Concerns\Approvable;
use Illuminate\Database\Eloquent\Model;
class PurchaseOrder extends Model
{
use Approvable;
public function approvalWorkflow(): string
{
return 'purchase-order';
}
public function approvableAttributes(): array
{
return ['amount', 'supplier_id', 'notes'];
}
}approvalWorkflow() declares which workflow this model uses, so callers need
not repeat the slug. approvableAttributes() is an allow-list for drafts;
returning null — the default — allows every attribute.
2. Create the workflow
Workflows are ordinary Eloquent models, so workflow management screens stay part of your application:
use ByRcsc\LaravelApproval\Models\ApprovalWorkflow;
$workflow = ApprovalWorkflow::create([
'name' => 'Purchase orders',
'slug' => 'purchase-order',
]);
$finance = $workflow->stages()->create([
'sequence' => 1,
'name' => 'Finance',
'required_approvals' => 1,
]);
$finance->approvers()->create([
'approver_type' => $financeUser->getMorphClass(),
'approver_id' => $financeUser->getKey(),
]);
$directors = $workflow->stages()->create([
'sequence' => 2,
'name' => 'Directors',
'required_approvals' => 2,
]);
foreach ($board as $director) {
$directors->approvers()->create([
'approver_type' => $director->getMorphClass(),
'approver_id' => $director->getKey(),
]);
}required_approvals is how many approvals the stage needs. Omit it, or set it
to null, and every resolved assignment must approve.
For workflows that belong in version control rather than in a management screen, see workflow definitions.
3. Submit
$order = PurchaseOrder::create([
'supplier_id' => $supplier->id,
'amount' => 4_000,
]);
$request = $order->submitForApproval(newAttributes: ['amount' => 10_000]);The record already exists and its amount is still 4000. The proposed
10000 is held on the request until the workflow completes. Omit
newAttributes entirely to submit the record itself rather than an edit to it.
Submission resolves the workflow from four places, most specific first: the
argument, the model's approvalWorkflow(), the approval.workflows config
map, then the workflows registered against the model's type. The requester
resolves from the argument, the model's approvalRequester(), then the
authenticated user.
4. Decide
use ByRcsc\LaravelApproval\Facades\Approval;
Approval::approve($request, $financeUser, 'Within the quarterly budget');Stage one is satisfied, so stage two activates. Two directors must approve:
Approval::approve($request, $firstDirector);
Approval::approve($request, $secondDirector);With the last stage satisfied, the draft is applied: $order->fresh()->amount
is now 10000, and the request state is approved.
The other two decisions both require a reason:
Approval::reject($request, $financeUser, 'Over budget for this quarter');
Approval::returnForRevision($request, $financeUser, 'Attach the supplier quote');5. Build an inbox
$requests = Approval::awaiting($user)
->withProgress()
->get();awaiting() returns pending requests whose active stage is waiting on this
user and which they have not already approved. withProgress() eager-loads the
stages, assignments, and actions that progress summaries and timelines read.
6. Ask before you render
Never render an approve button on hope. The eligibility read model runs the same calculation the decision runs, and explains a refusal:
$eligibility = Approval::eligibility($request, $user);
$eligibility->eligible; // bool
$eligibility->reason; // null, or an Ineligibility enum
$eligibility->stage; // the active stage, when the check got that far
$eligibility->stageProgress?->remainingApprovals;Through the shipped policy, the same question reads as
$user->can('approve', $request). See
eligibility and authorization.
7. Show progress
$request = Approval::pendingFor($order);
$request?->currentStage()->name; // "Directors"
$request?->pendingApprovers(); // holders who have not responded yet
$request?->stageProgress()?->approvalsReceived;
$request?->timeline(); // one entry per stage, in orderWhat to read next
- Workflows and stages for approval rules, rejection policies, and the snapshot boundary.
- Approvers and resolvers for assigning teams, roles, and reporting lines.
- Attribute drafts for conflicts, guard mode, and what happens when the record moves underneath a request.
- Testing for
Approval::fake().