byrcsc/laravel-approval
byrcsc/laravel-approval is a Laravel package for adding approval workflows to your application. Attach approvals to Eloquent models, manage pending and approved states, and keep approval logic organized within your existing Laravel app.
Architecture fit. This is a mature, opinionated domain package for Laravel applications that need multi-stage approval workflows (draft → submit → review → approve/reject/return → revise/resubmit/cancel/withdraw). It is not a thin helper: v1.0.0 ships a full schema (eight tables), an Approvable trait, an ApproverResolver contract, version-controlled workflow definitions, an authorization policy, notifications, SLA/escalation, delegation, and an append-only hash-chained audit log. Fit is strongest where approvals are a first-class product domain (expense reports, content publishing, procurement, HR requests, access grants) and weakest where approval is a minor incidental feature that could be satisfied by a boolean column and a queue job. The package assumes the host application owns the approvable models, the actors, and the attachment storage; it owns the state machine and the audit trail. That division is clean but implies the host must integrate at several seams (resolver, conditional stages, workflow selection, notifications, guard mode) rather than dropping it in blind.
Integration feasibility. High but non-trivial. Positives: Laravel 12 and 13, PHP 8.3 and 8.4, PHPStan max level, Pint, database-aware CI across SQLite/MySQL/PostgreSQL, and a workbench app that boots the package "the way a real application would" and exercises every seam. The workbench is the single strongest adoption signal here — it converts several days of "how does this actually wire together" discovery into a clone-and-run exercise. Publishable migrations, translations (approval-translations), and mail views (approval-views) mean no vendor-dir edits. The Approval::fake() facade plus exported factories give a credible testing story, which matters because approval state is exactly the kind of thing teams under-test.
Negative feasibility signals: the surface area is large. Eight tables, a policy, an event map, four escalation strategies, two resolver implementations, container-resolved condition classes, and a guard mode with an audited escape hatch is a lot of configuration surface for a v1.0.0. Identity customization (integer/UUID/ULID/string) is a genuine adoption enabler for teams locked into an existing PK convention, but it multiplies the schema variations the host must reason about. Authorization is convention-discovered — convenient if your conventions match, a debugging session if they don't.
Technical risk. The risks are less "will it work" and more "will the team understand it."
withoutApprovalGuard() is powerful and easy to misapply. The release notes are admirably honest that the final stale check is the backstop for writes outside model events — meaning the guard is a convenience, not a guarantee. Teams that treat it as a guarantee will ship data races.approval:sync is idempotent and dry-runnable and refuses destructive drift, which is the right design. The risk is operational: in-flight snapshots mean the running workflow and the code-defined workflow can legitimately diverge. Someone must own that divergence.Key questions. (1) Does our approval domain need versioned workflow definitions, or would a hardcoded state machine suffice? (2) Do we have a compliance requirement that the audit chain must satisfy, and does "tamper-evident within a documented trust boundary" clear it? (3) Who owns approval:sync in our release process, and how do we review drift refusals? (4) Are our primary keys integer, UUID, ULID, or mixed — and does the identity customization path cover all our approvable models? (5) Where does escalation run — which queue, which scheduler, what happens if it fires twice? (6) Do we want guard mode on day one, or should it be a phase-two hardening step? (7) What is the maintainer's support model and release cadence post-1.0.0?
Stack fit. Requires Laravel 12 or 13 and PHP 8.3/8.4. Laravel 11 is not claimed. Confirm the application's framework and PHP versions before anything else; this is the fastest disqualifier. Queue + scheduler must be healthy, because notifications defer until commit and escalation depends on scheduled scans. No exotic dependencies implied — this is a standard Eloquent + service-container package.
Migration path. There is no migration from a prior version (this is the first release), so the migration is from whatever homegrown approval logic exists. Recommended path:
composer build) and walk every seam against your own use cases. This is the cheapest possible spike and should gate the go decision.ApproverResolver before anything else. StaticUsersResolver is fine for a pilot; plan the move to CallbackResolver (org hierarchy, manager-of-manager, role-based) as its own work item.approval:sync in dry-run, then move to fluent definitions once the shape is stable.Compatibility. Multi-database support (SQLite/MySQL/PostgreSQL) is tested, which de-risks teams on MySQL 8 or Postgres with existing conventions. Row locks in the decision path imply the package expects a database with meaningful locking semantics — SQLite in production is a poor fit despite being supported in tests. String-identity coverage is explicitly tested, which is unusual and welcome for legacy schemas.
Sequencing. Pilot one model → validate authorization policy against your existing gate/policy conventions → add conditional stages → add delegation → add SLA and escalation → enable notifications → turn on guard mode. Guard mode and escalation are the two highest-blast-radius features; both should land after the team has production familiarity with the core state machine.
Maintenance. The host inherits two ongoing obligations: (a) keeping code-defined workflows and live requests reconciled via approval:sync, and (b) owning the scheduled jobs for escalation, stranded-stage detection, and approval:doctor. Neither is heavy, but both need a named owner. approval:status and approval:verify give operators real verbs for checking health and audit integrity, which is more than most packages offer at 1.0.
Support. The package documents its own trust boundaries and ships a workbench that exercises every integration seam — both signs of a maintainer who expects to be supported by reading, not by filing issues. The counterweight is that workbench is excluded from the dist archive and from CI, so it will drift. Budget for internal documentation of your wiring rather than relying on the demo staying accurate. PHPStan at max and Pint suggest the maintainer holds a quality bar; that generally correlates with responsive maintenance, but a 1.0.0 with this surface will still generate papercut issues.
Scaling. The decision path uses per-request transactions and row locks, which is correct and will hold under moderate concurrency. Watch three things at scale: (1) the pending-approver inbox queries — use the provided approvable scopes rather than hand-rolling, and index accordingly; (2) eager-loadable timelines — these are the read model most likely to become an N+1 problem if consumed carelessly; (3) escalation scans — "bounded" is good, but bound sizes need tuning as request volume grows, and overlapping scans must be prevented at the scheduler level. Bulk approve/reject is a per-request transaction loop, not a single bulk write; that is the safe choice and also means bulk operations on thousands of requests will be slow and should be queued.
Failure modes. The notable ones: guard-mode bypass used casually, producing writes that only the final stale check catches; workflow drift refused by approval:sync and left unresolved, blocking a deploy; escalation job downtime producing silent SLA breaches (mitigated by stranded-stage detection and approval:doctor); orphaned assignments when an approvable is hard-deleted (the package closes them, but only if the deletion goes through Eloquent); notification storms if the event map is enabled wholesale before audiences are tuned; and audit-chain verification failures that turn out to be legitimate out-of-band DB writes rather than tampering. None of these are design flaws — they are the operational edges of a system that correctly chose flexibility over hand-holding.
Ramp-up. Expect meaningful onboarding cost. A developer can get a toy workflow running in an afternoon via the workbench. A team can run a real workflow in production in two to three weeks. Full competence — conditional stages, workflow selection by priority, delegation semantics, escalation strategies, guard mode, and the audit trust boundary — is a one-to-two-month curve for one engineer who then owns the integration. The package rewards that investment with a genuinely complete feature set; it does not reward a "drop it in and see" approach. Recommendation: adopt with a named owner, pilot on one model, stage the riskiest features (guard mode, escalation, notifications) behind production familiarity, and validate the audit trust boundary against compliance requirements before committing.
How can I help you explore Laravel packages today?