›
›
›
  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

Workflow definitions.

Store workflows in source control and preview changes before syncing database rows.

Editable workflow rows suit an application with a workflow management screen. If your approval rules belong in code, declare them as definitions instead. Then run approval:sync to update the workflow rows to match.

Array definitions

// config/approval.php
'definitions' => [
    'purchase-order' => [
        'name' => 'Purchase orders',
        'description' => 'Finance, then the board above ₱100,000.',
        'stages' => [
            [
                'name' => 'Finance',
                'required_approvals' => 1,
                'sla_minutes' => 1440,
                'approvers' => [
                    ['resolver' => App\Approvals\FinanceTeam::class],
                ],
            ],
            [
                'name' => 'Board',
                'required_approvals' => 2,
                'rejection_policy' => 'any_rejection_vetoes',
                'condition' => App\Approvals\AmountExceeds::class,
                'condition_config' => ['threshold' => 100_000],
                'approvers' => [
                    ['resolver' => App\Approvals\BoardMembers::class],
                ],
            ],
        ],
    ],
],

The array key becomes the slug when the definition has no slug. Sequences default to array order and must be contiguous from one.

Workflow selection keys

A definition can also include the keys that decide when the package chooses it. These keys put workflow selection under version control with the stages:

'purchase-order-executive' => [
    'name' => 'Executive purchase orders',
    'approvable_type' => App\Models\PurchaseOrder::class,
    'condition' => App\Approvals\AmountAbove::class,
    'condition_config' => ['amount' => 10_000],
    'priority' => 10,
    'active' => true,
    'stages' => [/* … */],
],
Key
approvable_typeModel class or morph alias this workflow is registered against.
conditionA WorkflowCondition class deciding whether it applies.
condition_configConfiguration passed to that condition.
priorityHigher wins among equally specific workflows. Defaults to 0.
activeDefaults to true. An inactive workflow is never chosen.

The package validates condition at definition time. A class that does not exist or does not implement WorkflowCondition raises InvalidWorkflowDefinitionException. A negative priority raises the same exception.

Fluent definitions

WorkflowBuilder produces the same structure with a typed API:

use ByRcsc\LaravelApproval\Definitions\WorkflowBuilder;
use ByRcsc\LaravelApproval\Enums\EscalationAction;
use ByRcsc\LaravelApproval\Enums\RejectionPolicy;

WorkflowBuilder::make('purchase-order', 'Purchase orders')
    ->description('Finance, then the board above ₱100,000.')
    ->settings(['allow_self_approval' => false])
    ->stage('Finance', fn ($stage) => $stage
        ->minimumApprovals(1)
        ->sla(1440)
        ->remindersEvery(240)
        ->escalate(EscalationAction::Escalate, ['resolver' => ManagerOf::class])
        ->resolver(FinanceTeam::class))
    ->stage('Board', fn ($stage) => $stage
        ->minimumApprovals(2)
        ->rejectionPolicy(RejectionPolicy::AnyRejectionVetoes)
        ->when(AmountExceeds::class, ['threshold' => 100_000])
        ->approver($chairperson)
        ->resolver(BoardMembers::class));
Stage methodSets
minimumApprovals(?int)required_approvals; null means all
rejectionPolicy(RejectionPolicy)rejection_policy
approver(Model)A concrete approver entry
resolver(string, array)A resolver entry
sla(?int)sla_minutes
remindersEvery(?int)remind_every_minutes
escalate(EscalationAction, array)Escalation strategy and config
when(string, array)Condition class and config

Stages take their sequence from the order you declare them.

The builder has workflow-level methods for the selection keys above:

WorkflowBuilder::make('purchase-order-executive', 'Executive purchase orders')
    ->forApprovable(PurchaseOrder::class)
    ->when(AmountAbove::class, ['amount' => 10_000])
    ->priority(10)
    ->stage('Finance', fn ($stage) => $stage->approver($cfo));
Workflow methodSets
description(?string)description
active(bool)active; defaults to true
forApprovable(Model|string)approvable_type
when(string, array)condition and condition_config
priority(int)priority
settings(array)settings

when() appears in both tables and means different things at each level. On the workflow, it decides whether this workflow runs at all. On a stage, it decides whether that stage applies once the workflow is running. See choosing the workflow and conditional stages.

You can put builders in the config array or pass them to Approval::syncDefinitions().

Key every definition by slug. approval:sync reads definitions as a string-keyed map. The command drops an unkeyed list, such as 'definitions' => [WorkflowBuilder::make('purchase-order')], without a warning and reports everything already in sync.

Validation at definition time

The package validates definitions as you build them. A mistake fails where you wrote it, not at the next submission. InvalidWorkflowDefinitionException covers:

  • blank slugs, names, or stage names;
  • non-positive sequences, thresholds, SLAs, or reminder cadences;
  • sequences that are not unique and contiguous from one;
  • a stage with no approvers;
  • an approver entry naming both a model and a resolver, or neither;
  • a concrete approver that is not persisted, or whose record does not exist;
  • a resolver that does not implement ApproverResolver;
  • a condition that does not implement StageCondition;
  • an unknown escalation strategy or rejection policy;
  • configuration arrays with non-string keys;
  • one slug defined more than once.

The package checks concrete approvers against the database. If an approver has been deleted, the sync fails instead of producing a workflow that nobody can approve.

Previewing

php artisan approval:sync --dry-run
  create ......... workflow:purchase-order
  create ......... stage:purchase-order:1
  create ......... approver:purchase-order:1:resolver:App\Approvals\FinanceTeam:null
  Dry run complete; no approval workflow records were changed.

The dry run writes nothing. Run it in CI to fail a build when the definitions no longer match the deployed workflows.

Applying

php artisan approval:sync

The sync runs in one database transaction. The command exits with a non-zero code when it refuses any change.

Approval::syncDefinitions($definitions, $dryRun) is the programmatic form and returns a WorkflowSyncResult:

$result->created;      // list<string>
$result->updated;
$result->unchanged;
$result->refused;
$result->dryRun;
$result->successful(); // refused === []
$result->changed();    // created or updated is non-empty

What the sync refuses

The sync applies additive and supported configuration changes. It refuses these destructive changes:

  • removing a stage that exists in the database but not in the definition;
  • reordering existing stages;
  • removing an approver entry from an existing stage.

The package cannot judge the consequences of these changes. A stage removed from a definition might be a deliberate simplification or a typo in a config file. Make these changes yourself, against the workflow rows.

A refusal stops the whole sync before it writes anything. The sync never applies only part of the definitions.

In-flight requests are untouched

The sync edits workflow rows. Requests already running use their own snapshot and do not change, even when the sync changed their workflow.

To apply a workflow change to a running request, call Approval::resync(). A resync is a separate action that records who performed it.

Deploy sequence

For an application that keeps its workflows in code, run the commands in this order:

php artisan approval:sync --dry-run   # in CI, on the pull request
php artisan migrate --force
php artisan approval:sync             # after deploy
php artisan approval:status           # confirm nothing needs attention

What to read next

  • Workflows and stages for every field a definition can persist.
  • Choosing the workflow to define conditions and priorities.
  • Console commands to automate preview, sync, and status checks during deployment.
PreviousNotification contentNextAudit trail and evidence
View source

On this page

  1. Array definitions
  2. Workflow selection keys
  3. Fluent definitions
  4. Validation at definition time
  5. Previewing
  6. Applying
  7. What the sync refuses
  8. In-flight requests are untouched
  9. Deploy sequence
  10. What to read next