cjmellor/approval
Approval is a Laravel package that provides a simple way to approve new Model data before it is persisted. Add an approval workflow to any Eloquent model, keeping unapproved changes out of your database until reviewed. Requires PHP 8.3+ and Laravel 12.4+.
composer require, publish migrations, add MustBeApproved to a model). That turns a multi-sprint build into a config-and-policy decision.in_review, needs_info). Good fit for editorial publishing, marketplace listing review, user-profile/company-detail change review, supplier onboarding data, and any "dirty data is captured now, applied later" flow.original_data + new_data as JSON, audited_by, creator_id/creator_type, and rolled_back_at give an out-of-the-box audit trail. This can justify pulling an "auditability" epic forward without buying a GRC platform.expiresIn(hours: 24)->thenReject(), thenPostpone(), thenCustom()) plus approval:process-expired on a scheduler let you define policy — "unreviewed price changes auto-reject after 48h" — as a config value rather than an engineering project. Approval::expired() / hasExpiration() support ops dashboards.protected array $approvalAttributes = ['name'] lets you make a product decision like "only display name and pricing changes require approval, bio edits are instant," and enforce it declaratively.withoutApproval(), isApprovalBypassed(), and per-model getApprovalForeignKeyName() (e.g. author_id instead of user_id) support trusted-user fast paths and non-standard data models without forking.ApprovalCreated, ModelApproved, ModelSetPending, ModelRejected, ModelRolledBack, ApprovalExpired define the seams where your notifications, Slack pings, and analytics hook in. That's roadmap input: notification delivery is explicitly out of scope and must be staffed.Adopt when most of these hold:
approvals table as the source of truth for pending change requests.rollback(), rollback(bypass: false), conditional rollbacks, rolled_back_at.Look elsewhere or plan to build when:
spatie/laravel-permission — but recognize that authorization is entirely your build.UPGRADE.md migration path) all point to "wrap it in an internal adapter interface" rather than letting it leak across the domain.To executives: We can stand up a review-and-approve workflow for [content / listings / profile changes] in days instead of a sprint or two, at zero license cost (MIT). It ships with the things audit and compliance will ask for later — a record of what changed, who changed it, who approved it, when it was rolled back, and automatic rejection of stale requests after a deadline. It's reversible: it's a Composer package behind an internal interface, so we can swap it out without touching product surfaces. The main non-negotiable is that it requires Laravel 12.4+; if we're behind on framework upgrades, that becomes the critical path, not the approval feature.
To engineering: Add the MustBeApproved trait to a model and every dirty attribute on create/update lands in a polymorphic approvals table as original_data/new_data JSON with a state enum — Pending, Approved, Rejected, plus configurable states. You get query scopes (approved(), pending()), transition helpers (approve(), rejectIf(), postponeUnless()), events for all state changes, rollback with re-approval control, and an approval:process-expired command for the scheduler. What you don't get: the authorization model, the "apply this data to the real model" logic, notifications, or any UI. Those are ours — so let's define an ApprovalPolicy service boundary now, restrict usage to explicitly declared $approvalAttributes, and avoid depending on Approval models outside that layer. Also budget the Laravel 12.4 upgrade and pin ^2.1, since v2 introduced schema changes and dropped Laravel 11.
How can I help you explore Laravel packages today?