Installation
Require the package. The service provider, the Approvals facade alias and the global approvals() helper are auto-discovered — nothing to register:
composer require roundly-consulting/approvals-for-laravel
php artisan vendor:publish --tag="approvals-migrations"
php artisan migratePublish-only migrations
The migrations are not loaded automatically — php artisan migrate creates nothing until you have published them. The seven files land in database/migrations with timestamps that preserve their order, so migrate creates the four tables and applies each schema change after the table it alters:
| # | Migration | Effect |
|---|---|---|
| 1 | 0001_create_approvals_table | The approvals decision ledger. |
| 2 | 0002_create_approval_requests_table | Multi-approver approval_requests. |
| 3 | 0003_add_v11_columns_to_approvals_table | Adds decided_by (delegation), weight and approval_request_stage_id. |
| 4 | 0004_add_staging_to_approval_requests_table | Adds staged, reject_on_stage_rejection and workflow. |
| 5 | 0005_create_approval_request_stages_table | The approval_request_stages table for staged pipelines. |
| 6 | 0006_create_approval_delegations_table | The approval_delegations table. |
| 7 | 0007_change_approval_request_id_to_bigint | On PostgreSQL with key_type uuid, turns approvals.approval_request_id into a bigint; a no-op everywhere else. |
Do the models that give or receive approvals use UUID or ULID keys? Set key_type before you migrate — it types every polymorphic id column (see Models & schema).
Upgrading from 1.0.0
Version 1.0.1 adds migration 0007. Publish it — the migrations you already published stay as they are — and migrate. It only acts on PostgreSQL with key_type uuid, where every decision on a request failed under 1.0.0; everywhere else it does nothing:
php artisan vendor:publish --tag="approvals-migrations"
php artisan migrateMySQL: migrate failed with SQLSTATE 1071
Up to 1.1.0, 0001_create_approvals_table failed on MySQL 8 with utf8mb4 (SQLSTATE 1071, “Specified key was too long; max key length is 3072 bytes”): its approvals_actor_approvable_status_index came to 3076 bytes. Version 1.1.1 fixes the migration in place — the index now covers actor_id, actor_type, approvable_id and approvable_type, and status keeps its own index. MySQL does not roll DDL back, so the failed run left an empty approvals table behind. To recover, update the package, republish the migrations over your copies, drop that table and migrate:
composer update roundly-consulting/approvals-for-laravel
php artisan vendor:publish --tag="approvals-migrations" --force
php artisan db # then: DROP TABLE approvals; (empty, left by the failed run)
php artisan migrateEvery other install — PostgreSQL, or MySQL on utf8mb3, where the old 0001 ran — needs nothing. It keeps the five-column index under the same name, which serves the same queries, and no later migration touches it.
Config (optional)
The package runs with zero host configuration. Publish the config only when you want to change a default:
php artisan vendor:publish --tag="approvals-config"The only publish tags are approvals-migrations and approvals-config — the package ships no views or translations.
Next steps
- Add GivesApprovals to the model that decides and HasApprovals to the model that is decided on.
- Add RequiresApproval to anything that needs sign-off from several approvers.
- Record your first decision with the Approvals facade.
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.