Delegation
An approver can hand their authority to another model for a window. While the delegation is active, the delegate’s decisions count as the delegator — the approval records both the delegator (as actor) and the delegate (as decided_by):
use Carbon\CarbonImmutable;
use RoundlyConsulting\Approvals\Facades\Approvals;
Approvals::delegations($manager)->to($assistant)->until(CarbonImmutable::now()->addWeek())->grant();
// or ->from($start), ->for($seconds) (measured from the start), or no window at all
$approval = Approvals::for($release)->as($assistant)->approve(); // counts as $manager
$approval->wasDelegated(); // true
$approval->actor; // $manager
$approval->decidedBy; // $assistant
Approvals::for($release)->as($assistant)->cancel(); // the delegate can withdraw what it decided for $managerSetting the window
Approvals::delegations($delegator)->to($delegate) collects the window with from(), until() or for(); nothing is written until grant(), which saves the ApprovalDelegation and fires ApprovalDelegated once, with the final window. Without a window the delegation is open-ended. The trait’s delegateApprovalsTo() grants at once:
use Carbon\CarbonImmutable;
use RoundlyConsulting\Approvals\Facades\Approvals;
// A fixed leave window.
Approvals::delegations($manager)->to($assistant)
->from(CarbonImmutable::parse('2026-08-03'))
->until(CarbonImmutable::parse('2026-08-14'))
->grant(); // ApprovalDelegation
// One hour from now.
$delegation = Approvals::delegations($manager)->to($assistant)->for(3600)->grant();
// GivesApprovals shorthand — grants at once and returns the ApprovalDelegation
$manager->delegateApprovalsTo($assistant); // open-ended
$manager->delegateApprovalsTo($assistant, until: CarbonImmutable::now()->addWeek());Revoking and listing
Approvals::delegations($manager)->revoke(); // revoke all — active and scheduled; returns the count
Approvals::delegations($manager)->revoke($assistant); // revoke one delegate
Approvals::delegations($manager)->active(); // Collection<ApprovalDelegation> in force now, newest first
Approvals::delegationFor($assistant); // ?ApprovalDelegation the assistant acts under now
$manager->revokeApprovalDelegation($assistant); // GivesApprovals shorthand
$manager->approvalDelegations; // every delegation handed out
$delegation->isActiveAt(); // bool — started, not ended, not revoked
$delegation->isActiveAt(CarbonImmutable::parse('2026-08-10'));
$delegation->revoke(); // sets revoked_at on this one modelrevoke() revokes the delegator’s delegations that are in force or scheduled to start later — or only those to one delegate — sets revoked_at, fires ApprovalDelegationRevoked per revocation and returns the count. It never touches another delegator’s delegations. Calling revoke() on a single ApprovalDelegation model sets revoked_at without firing the event.
Rules of attribution
- A delegation is in force when it is not revoked, has started (starts_at empty or past) and has not ended (ends_at empty or still ahead).
- On a request that names its approvers, a delegate may decide for a named delegator, and a model that is itself a named approver always decides as itself, even when it also stands in for someone.
- Elsewhere, every decision by the delegate counts as the delegator; when several delegations apply, the most recently created one wins. The weight is resolved from the delegator.
- The authorization gate checks the acting delegate, before the decision is re-attributed.
- The delegate can withdraw what it decided for the delegator with cancel() or cancelApproval().
- ask() is addressed to the asked model itself, never to whoever it stands in for.
Validation
grant() validates before writing: delegating to yourself and a window that ends before it starts both throw InvalidDelegationException and leave no row behind:
use Carbon\CarbonImmutable;
use RoundlyConsulting\Approvals\Facades\Approvals;
Approvals::delegations($manager)->to($assistant)
->from(CarbonImmutable::parse('2026-08-14'))
->until(CarbonImmutable::parse('2026-08-03')) // ends before it starts
->grant(); // throws InvalidDelegationException — no row is written
Approvals::delegations($manager)->to($manager)->grant(); // throws InvalidDelegationException — self-delegationShow 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 cryptoBy donating, you agree to our donation terms.
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.