---
title: "The Approvals facade — Approvals for Laravel | Roundly"
description: "The whole Approvals facade — decisions, multi-approver requests, reads, delegation and expiry — with every method and builder."
url: https://roundly-consulting.com/open-source/docs/approvals-for-laravel/facade
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Approvals for Laravel](https://roundly-consulting.com/open-source/docs/approvals-for-laravel.md)

# The Approvals facade

Everything goes through one API — the Approvals facade, the injectable ApprovalsManager behind it, or the action classes it runs. All three execute the same code, and the facade is the recommended entry point. It is registered automatically. The full surface:

```php
use RoundlyConsulting\Approvals\Facades\Approvals;

// One actor's decision on one approvable
Approvals::for($deployment)->as($user)->because('Looks good to me')->approve();
Approvals::for($deployment)->as($user)->because('Please add tests')->reject(); // withdraws the approval above
Approvals::for($deployment)->as($user)->ask();                 // ask the actor: a pending decision
Approvals::for($deployment)->as($user)->cancel();              // withdraw the actor's live decision
Approvals::for($deployment)->as($user)->toggle();              // simple on/off
Approvals::for($deployment)->as($user)->expiresIn(86400)->approve(); // valid for a day
Approvals::for($deployment)->as($user)->weight(3)->approve();  // override the decision's weight
Approvals::for($invoice)->as($user)->within($request)->approve(); // pin to one request
Approvals::for($purchase)->close();                            // your app calls off the open round — no actor

// Reads
Approvals::for($deployment)->as($user)->isApproved();  // bool
Approvals::for($deployment)->as($user)->isRejected();  // bool
Approvals::for($deployment)->hasPending();             // bool — any pending decision

// Multi-approver requests (flat, staged, or from a workflow preset)
Approvals::request($invoice)->from([$a, $b, $c])->quorum(2)->expiresIn(3600)->open();
Approvals::request($release)->stages([...])->continueOnRejection()->expiringAt($t)->open();
Approvals::request($budget)->workflow('payout')->expiresIn(3600)->open([$a, $b, $c]);

Approvals::status($invoice);        // ApprovalStatus of the latest request (Pending when none)
Approvals::progress($invoice);      // ?ApprovalProgress
Approvals::currentStage($release);  // ?ApprovalRequestStage
Approvals::preset('payout');        // WorkflowPreset from config

// Delegation
Approvals::delegations($boss)->to($deputy)->from($monday)->until($friday)->grant();
Approvals::delegations($boss)->revoke($deputy);   // or ->revoke() for all; returns int
Approvals::delegations($boss)->active();          // Collection<ApprovalDelegation>
Approvals::delegationFor($deputy);                // ?ApprovalDelegation in force now

// Housekeeping
Approvals::expire();                // lapse overdue asks, approvals and requests; returns int
Approvals::expire(subjectType: Invoice::class); // only those on invoices (class or morph alias)
```

## Facade methods

| Method | Returns | Purpose |
| --- | --- | --- |
| `for($approvable)` | PendingApproval | Start a decision on an approvable; set the actor with as(). |
| `as($actor)` | PendingApproval | Start a decision by an actor; set the approvable with for(). |
| `request($subject)` | PendingApprovalRequest | Open a multi-approver request — flat, staged or from a preset. |
| `status($subject)` | ApprovalStatus | Status of the subject’s latest request; Pending when it has none, Expired once an open request is past its expiry. |
| `progress($subject)` | ?ApprovalProgress | Progress snapshot of the latest request; its status reads Expired once the request is overdue. |
| `currentStage($subject)` | ?ApprovalRequestStage | The open stage of the latest staged request; null once it is closed or overdue. |
| `preset($name)` | WorkflowPreset | Resolve and validate a preset from config('approvals.workflows'). |
| `delegations($delegator)` | DelegationsHandle | Grant, revoke and list one delegator’s delegations. |
| `delegationFor($delegate, ?$at)` | ?ApprovalDelegation | The delegation the model acts under at $at (now when omitted). |
| `expire(?$now, ?$subjectType)` | int | Lapse overdue asks, approvals and requests; returns how many. With $subjectType (a model class or morph alias) only those on that type. |
| `fake()` | ApprovalsFake | Swap in the recording fake; recorded() and every assert\*() are then callable on the facade too (see Testing). |

## Decisions — PendingApproval

Start from the approvable with for() or from the actor with as(), chain the context, then call a terminal method. A request that names its approvers refuses anyone else with UnauthorizedApprovalException, and a closed request refuses every decision with ClosedApprovalRequestException:

| Method | Returns | Purpose |
| --- | --- | --- |
| `for($approvable)` | self | Set the model being decided on. |
| `as($actor)` | self | Set the deciding model. close() refuses one. |
| `within($request)` | self | Pin the decision to this request instead of the approvable’s latest open one. It must belong to the approvable (else InvalidApprovalRequestException) and still be open (else ClosedApprovalRequestException). With close(), only this request is closed. |
| `because($reason)` | self | Attach a reason — the decision’s (approve(), reject(), ask(), toggling on) or the withdrawal’s (cancel(), toggling off). An answer to an ask records only its own reason, or none. close() refuses one; because(null) is no reason. |
| `weight($n)` | self | Override the weight this decision carries — approve(), reject() and toggling on. ask(), cancel() and close() refuse it. |
| `expiresIn($seconds)` | self | Expire N seconds from now — applied by approve(), ask() (as its reply-by deadline) and toggling on. reject(), cancel() and close() refuse it. |
| `expiringAt($at)` | self | Absolute expiry — applied and refused by the same calls as expiresIn(). |
| `approve()` | Approval | Record an approval. It counts towards the pinned request, else the approvable’s latest open request; repeating it is a no-op. |
| `reject()` | Approval | Record a rejection with because() and weight() — an approval the actor held is withdrawn. A rejection has no expiry, so an expiry set before it throws InvalidApprovalRequestException. |
| `ask()` | Approval | Ask the actor for a pending decision, with because() as its reason and the expiry as its reply-by deadline; returns the actor’s live decision unchanged when it already holds one. weight() throws InvalidApprovalRequestException. |
| `cancel()` | ?Approval | Withdraw the actor’s live decision — or one it made as a delegate under a delegation still in force — in the round a decision would land in (only the open stage of a staged request), with because() as the withdrawal’s reason; null when there is none. weight() or an expiry throws InvalidApprovalRequestException. |
| `close(?$outcome)` | int | Close the approvable’s open rounds from your app — or only the within() request — as Cancelled (the default) or Expired. Needs only for(); returns how many rounds this call closed, 0 when none was open. because(), as(), weight() and an expiry throw InvalidApprovalRequestException. |
| `toggle()` | bool | Simple on/off in the within() request, else the open one — true approved (through approve(), taking because(), weight() and the expiry), false withdrawn (firing ApprovalCancelled, with because() as the withdrawal’s reason). |
| `isApproved()` | bool | Whether the actor holds an approval of the approvable that is still in force. |
| `isRejected()` | bool | Whether the actor holds a rejection for the approvable. |
| `hasPending()` | bool | Whether the approvable has any pending decision not yet past its deadline — needs only for(). |

## Requests — PendingApprovalRequest

| Method | Returns | Purpose |
| --- | --- | --- |
| `from([...])` | self | The named approvers of a flat request — only they (or their delegates) can decide it. Each is stored once; required\_approvers defaults to their count. |
| `rule($rule, ?$quorum)` | self | The flat request’s rule and threshold — Unanimous by default. |
| `any() / quorum($n) / weighted($n)` | self | Shortcuts for rule(Any), rule(Quorum, $n) and rule(Weighted, $n). |
| `stages([StageDefinition, ...])` | self | Make it a staged request. Combined with from(), open() throws InvalidApprovalRequestException. |
| `continueOnRejection()` | self | Staged: keep going past a rejected stage. |
| `expiresIn($s) / expiringAt($t)` | self | Stamp expires\_at on the request. Set before workflow(), it carries over and replaces the preset’s expiry. |
| `open()` | ApprovalRequest | Open the flat or staged request; a threshold its approvers could never reach is refused, and so is a request its rule could never approve — e.g. no names under Unanimous. |
| `workflow($name)->open([...])` | ApprovalRequest | Open from a named preset — a flat list, or one list per stage. workflow() throws InvalidApprovalRequestException when from(), rule(), any(), quorum(), weighted(), stages() or continueOnRejection() was set first: the preset defines them. |
| `workflow($name)->expiresIn($s) / expiringAt($t)` | PendingWorkflowRequest | Give this round its own deadline instead of the preset’s expiry; the last expiry set wins. |

## Delegation — DelegationsHandle

| Method | Returns | Purpose |
| --- | --- | --- |
| `delegations($d)->to($delegate)` | PendingDelegation | Start a delegation — nothing is written until grant(). |
| `->from($t) / ->until($t) / ->for($seconds)` | PendingDelegation | Set the window; for() counts from the start (now without from()). |
| `->grant()` | ApprovalDelegation | Validate the window, save it and fire ApprovalDelegated. |
| `delegations($d)->revoke(?$delegate)` | int | Revoke this delegator’s active and scheduled delegations — or only those to $delegate. |
| `delegations($d)->active(?$at)` | Collection<ApprovalDelegation> | Delegations in force at $at (now), newest first. |

Pinning to a request, the approvals() helper, absolute expiry, incomplete builders and the settings each call refuses are covered under Fluent builders; the same API without the facade is under DI and actions.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/approvals-for-laravel.md)
