NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

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 migrate

Publish-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:

#MigrationEffect
10001_create_approvals_tableThe approvals decision ledger.
20002_create_approval_requests_tableMulti-approver approval_requests.
30003_add_v11_columns_to_approvals_tableAdds decided_by (delegation), weight and approval_request_stage_id.
40004_add_staging_to_approval_requests_tableAdds staged, reject_on_stage_rejection and workflow.
50005_create_approval_request_stages_tableThe approval_request_stages table for staged pipelines.
60006_create_approval_delegations_tableThe approval_delegations table.
70007_change_approval_request_id_to_bigintOn 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 migrate

MySQL: 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 migrate

Every 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 crypto

By 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.