›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-approval
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Workflows and stages
  • Choosing the workflow
  • Approvers and resolvers
  • Requests and state
  • Conditional stages

Approval flow

  • Submitting for approval
  • Recording decisions
  • Attribute drafts
  • Returns and resubmission
  • Bulk decisions

Assignments and deadlines

  • Delegation and reassignment
  • SLAs and escalation

Reading and authorization

  • Eligibility and authorization
  • Queries and timelines
  • Events and listeners
  • Notifications
  • Notification content

Operations

  • Workflow definitions
  • Audit trail and evidence
  • Console commands
  • Testing
  • Troubleshooting

byrcsc/laravel-approval · 1.x

Choosing the workflow.

Choose which workflow a model uses when several workflows could apply.

You can name a workflow when submitting a model or let Laravel Approval choose one. Automatic selection lets the same model follow different processes. For example, a small purchase order can use a short workflow while a larger one also needs executive approval.

Approval::submit($order);                              // work it out
Approval::submit($order, 'purchase-order-executive');  // use this one

A named workflow is used as named

The argument always wins. When you name a workflow, Laravel Approval does not check its selection condition or compare its priority with other workflows.

The active check still applies. Submission refuses an inactive workflow with InvalidWorkflowException::inactive(), whether you named the workflow or the package selected it. Naming a workflow skips selection, not validation. Reactivate a disabled workflow before you submit a request to it.

The package never replaces a named workflow with another workflow. If it cannot run, submission throws an exception.

Where candidates come from

With nothing named, the package gathers candidates in this order:

  1. the model's approvalWorkflow() method, returning an ApprovalWorkflow or a slug;
  2. the approval.workflows config map, keyed by class name or morph alias;
  3. every workflow whose approvable_type column names the model.
// 1. On the model
public function approvalWorkflow(): string
{
    return 'purchase-order';
}

// 2. In config/approval.php
'workflows' => [
    App\Models\PurchaseOrder::class => 'purchase-order',
],

// 3. On the workflow row
ApprovalWorkflow::create([
    'slug' => 'purchase-order',
    'approvable_type' => PurchaseOrder::class,
]);

Use the config map for models you do not own, or to keep every mapping in one file. Use approvable_type when a model has more than one workflow, since it is the only source that can name several.

A slug that names no workflow raises WorkflowNotFoundException. The package does not skip it. A typo in a model or in configuration is a mistake, not a reason to move on to the next source.

Naming several is a preference order

The config map accepts a list. Each entry is its own step in the preference order:

'workflows' => [
    App\Models\PurchaseOrder::class => [
        'purchase-order-capital',
        'purchase-order',
    ],
],

The first workflow that applies wins. The package tries the second only when the first is inactive or its condition rules it out. Each named entry has its own level of specificity, so the list has a clear order and cannot be ambiguous.

Workflows registered by approvable_type are different. They share the last position, so conditions and priority choose between them as a set.

If a workflow is reachable from more than one source, the package considers it only at its most specific position. Listing a workflow in config and also registering it by type does not give it two chances.

Filtering and ordering

The package then filters the candidates. It removes inactive workflows and workflows whose condition does not apply to the current submission. It orders the remaining candidates by source, and then by priority, highest first.

When no candidate remains, submission raises WorkflowNotFoundException.

Registering several against one model

Register each workflow against the model type and give it the condition that selects it. Priority orders the workflows that remain:

ApprovalWorkflow::create([
    'name' => 'Executive purchase orders',
    'slug' => 'purchase-order-executive',
    'approvable_type' => PurchaseOrder::class,
    'condition' => AmountAbove::class,
    'condition_config' => ['amount' => 10_000],
    'priority' => 10,
]);

ApprovalWorkflow::create([
    'name' => 'Standard purchase orders',
    'slug' => 'purchase-order-standard',
    'approvable_type' => PurchaseOrder::class,
    'priority' => 0,
]);

A ₱25,000 order matches both, and the executive workflow's higher priority wins. A ₱400 order fails the condition, so the standard workflow is the only candidate. The unconditional low-priority workflow is the fallback. Without it, a submission that matches no condition has no workflow.

Writing a condition

A workflow condition implements WorkflowCondition:

use ByRcsc\LaravelApproval\Contracts\WorkflowCondition;
use ByRcsc\LaravelApproval\Models\ApprovalWorkflow;
use Illuminate\Database\Eloquent\Model;

final class AmountAbove implements WorkflowCondition
{
    public function applies(
        ApprovalWorkflow $workflow,
        Model $approvable,
        ?Model $requester,
        array $config,
    ): bool {
        return $approvable->amount > ($config['amount'] ?? 0);
    }
}

Laravel Approval resolves conditions from the container, so constructor injection works.

Take care with $approvable and $requester.

$approvable is the proposal, not the record. When the submission has an attribute draft, the package fills the drafted values onto a clone before the condition runs. Conditional stages work the same way. Take a purchase order saved at ₱4,000 and submitted with a draft that raises it to ₱20,000. The condition sees ₱20,000, because the request asks for approval of the proposed value.

$requester may be null. The requester is null when nobody is submitting yet. For example, it is null when your application asks which workflows a record could be submitted against. A condition that uses the requester without a null check works at submission and fails in your workflow picker.

public function applies(
    ApprovalWorkflow $workflow,
    Model $approvable,
    ?Model $requester,
    array $config,
): bool {
    // Nobody is submitting yet: offer the workflow rather than hiding it.
    return $requester?->isContractor() ?? true;
}

Ties are refused, not broken

Two workflows that are equally specific and equally prioritized raise AmbiguousWorkflowException:

More than one approval workflow applies to [App\Models\PurchaseOrder] at
priority 0: [purchase-order-capital], [purchase-order-operating]. Give one of
them a higher priority, narrow them with a condition, or name the workflow at
the call site.

If the package picked one arbitrarily, insertion order would decide which workflow ran. The same record could then follow a different workflow tomorrow for no configured reason. The message names the fixes.

Only the type-registered position can hold more than one workflow, so ambiguity can arise only there.

Asking before you submit

Build a workflow picker from these queries:

Approval::workflowsFor($order);
// Collection of applicable workflows, best first. Nobody is submitting yet,
// so conditions receive a null requester.

Approval::workflowsFor($order, $requester, ['amount' => 20_000]);
// Applicable to this specific submission, draft and all.

Approval::workflowFor($order);
// The single workflow a submission would use. Raises on ambiguity.

Approval::workflows();
// Builder over every active workflow, ordered by name. The maintenance-screen
// query, not the one to offer a requester.

workflowsFor() returns all matching workflows in order. workflowFor() returns the first match. Use workflowsFor() to render a choice and workflowFor() to preview automatic selection.

Pass the same draft to workflowsFor() that you will pass to submit(). Conditions judge the proposal. If you compute the choice without the draft and then submit with it, submission can select a different workflow than the one you showed.

In a definition file

The fluent builder sets every selection key, so workflow selection stays under version control with the rest of the definition:

WorkflowBuilder::make('purchase-order-executive')
    ->forApprovable(PurchaseOrder::class)
    ->when(AmountAbove::class, ['amount' => 10_000])
    ->priority(10)
    ->stage('Finance', fn ($stage) => $stage->approver($cfo));

forApprovable() accepts a model instance or a class-string. See workflow definitions for the array equivalent and how approval:sync reconciles changes.

Workflow conditions versus stage conditions

The two condition types answer different questions and run at different times.

A workflow condition decides which workflow runs. The package checks it only while choosing a workflow, never for a workflow named at the call site. It runs before the request exists.

A stage condition decides whether a stage inside the running workflow applies. It runs once, during submission. The package creates a stage that fails its condition in the skipped state.

A workflow can use both. For example, a workflow condition selects the workflow for capital purchases, and stage conditions skip legal review inside it.

What to read next

  • Workflows and stages for the fields used during workflow selection.
  • Conditional stages to apply conditions inside the selected workflow.
  • Submitting for approval to name a workflow or use automatic selection.
  • Workflow definitions to store selection rules in source control.
PreviousWorkflows and stagesNextApprovers and resolvers
View source

On this page

  1. A named workflow is used as named
  2. Where candidates come from
  3. Naming several is a preference order
  4. Filtering and ordering
  5. Registering several against one model
  6. Writing a condition
  7. Ties are refused, not broken
  8. Asking before you submit
  9. In a definition file
  10. Workflow conditions versus stage conditions
  11. What to read next