Fluent builders
Approvals::for(), as() and request() return small builders that collect context and write nothing until a terminal call — approve(), reject(), ask(), cancel(), toggle() or open(). Every terminal goes through ApprovalsManager, so Approvals::fake() records it. The method tables are under The Approvals facade.
Pinning a decision to one request
By default a decision joins the approvable’s latest open request. within() pins it to a specific one — which must belong to the approvable and still be open, so a decision on one model never counts towards another model’s request and nothing lands in a finished round:
use RoundlyConsulting\Approvals\Facades\Approvals;
$request = Approvals::request($release)->from([$lead, $qa])->open();
Approvals::for($release)->as($lead)->within($request)->because('Ship it')->approve();
Approvals::for($otherRelease)->as($lead)->within($request)->approve();
// InvalidApprovalRequestException — the request belongs to another subject; nothing is written
// …later, once $request has resolved:
Approvals::for($release)->as($qa)->within($request)->reject();
// ClosedApprovalRequestException — its round is over; nothing is writtenThe approvals() helper
A global approvals() helper resolves the same singleton manager — handy where you prefer a function to a facade:
approvals()->for($deployment)->as($user)->approve(); // the same manager as the facade
Approvals::as($user)->for($deployment)->approve(); // start from either sideAbsolute expiry
expiresIn() takes seconds from now; expiringAt() takes any Carbon instance. Both exist on the decision and the request builder:
use Carbon\CarbonImmutable;
use RoundlyConsulting\Approvals\Facades\Approvals;
Approvals::for($budget)->as($cfo)
->because('Within the quarterly limit')
->expiringAt(CarbonImmutable::parse('2026-12-31 23:59'))
->approve();Incomplete builders
Decision terminals need both an actor and an approvable. A missing one throws IncompletePendingApprovalException; hasPending() only needs the approvable:
use RoundlyConsulting\Approvals\Facades\Approvals;
Approvals::for($deployment)->approve();
// IncompletePendingApprovalException: No actor was set on the pending approval. Call as() first.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 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.