Installation
Requirements
| Supported | |
|---|---|
| PHP | 8.4, 8.5 |
| Laravel | 13.x |
| Databases | SQLite · PostgreSQL · MySQL 8.4 LTS |
Every database-touching path is tested against a real PostgreSQL and a real MySQL 8.4 — not just SQLite — so it runs on Laravel Cloud (serverless Postgres plus MySQL 8.4 LTS) out of the box. PostgreSQL is primary, but the append-only and one-active-version guarantees are enforced portably in the app layer on every engine.
Your users may be keyed however you key them
The ledger records the subject by subject_type + subject_id, and subject_id is a
64-character string column. An auto-increment id, a UUID and a ULID all fit, so the package
imposes no key type on the model you pass it — HasUuids needs no adapter and no config.
The proof is unaffected by which you use: the tamper-evidence chain hashes the string cast of
every field, precisely so a value one driver returns as 2 and another as '2' produces the same
hash. Upgrading from a version before 0.18 widens the column in place and rewrites no row.
MariaDB is not supported. It speaks the MySQL wire protocol, but Laravel carries mariadb as
its own driver name, so a MariaDB connection never travels the MySQL code path — and the
proof-column trigger this package's evidentiary weight rests on is written per engine. The
migration stops with a named exception rather than leaving frozen rows editable.
Install
composer require pushery/legal-consent-for-laravel
The service provider is registered automatically.
Publish and migrate
Publish everything at once, or only the group you need, then run the migrations:
php artisan vendor:publish --tag=legal-consent # config + migrations + views + lang
php artisan vendor:publish --tag=legal-consent-config # or just one group
php artisan migrate
Have WireKit installed? Take the group-by-group route instead — the umbrella tag publishes views, and that changes which ones are served.
What the umbrella tag deliberately leaves out
The umbrella tag covers the standard set only. Two groups stay separate because publishing them unasked would be destructive:
legal-consent-users-cache— drops a column from youruserstable.legal-consent-backfill— backfills historical rows.
legal-consent-wirekit is separate for a different reason: it overwrites the plain stubs
with their WireKit twins, and that override only ever happens when you ask for it.
With WireKit installed, publish the config and the language files — not the views
legal-consent.ui.variant defaults to auto and serves the WireKit-native views whenever
WireKit ≥ 2.26.0 is installed, so nothing has to be published to get the WireKit look — see
User interface.
Publishing views is what takes it away again. The umbrella tag — and legal-consent-views — copies
the whole resources/views tree into resources/views/vendor/legal-consent/, and the top level of
that tree is the plain stubs. A published view is resolved before either of the package's own
sets, by design, so those plain copies now win over the WireKit views auto would have served. The
WireKit twins arrive in the same copy, one directory down under
views/vendor/legal-consent/wirekit/, where nothing looks for them. Nothing errors and
legal-consent:doctor reports nothing — the screens simply render unstyled.
So on a WireKit application, publish the two groups you actually edit:
php artisan vendor:publish --tag=legal-consent-config
php artisan vendor:publish --tag=legal-consent-lang
php artisan migrate
If you do publish views — to customize them, which is the only reason to — pull the WireKit twins over the published plain copies afterwards:
php artisan vendor:publish --tag=legal-consent-wirekit --force
--force is not optional there: the files already exist from the view publish, and without it the
command leaves them exactly as they are. Pinning legal-consent.ui.variant to plain or wirekit
decides the question outright instead of leaving it to what happens to be published.
The individual tags are legal-consent-config, legal-consent-migrations, legal-consent-views,
legal-consent-lang, and legal-consent-mail — the last one takes the change-notice mail shell
and its theme without the consent screens, for an app that only wants to brand the notice. See
User interface for what the view and language tags contain.
Publish your texts — the step that has no error message
After migrate, legal_documents is empty. Consent::published() returns null for every
document, and the read path does not fall back to your source files on purpose (a page shows the
text of the locale it claims, or nothing). So every legal page you build renders empty: no error,
no log, no warning. The install looks finished, the configuration is correct, the Markdown is
sitting there — and the pages are shells.
Freeze the whole configured matrix once:
php artisan legal-consent:publish --all --editorial
--editorial is the right classification for a first publish: nobody has agreed to anything yet,
so there is nothing to notify about. It is idempotent — text that is already the active version
is left untouched. That matters most if your app is cloned: every fresh CI database and every new
staging box starts from the empty state, and the failure only shows when someone opens a legal page.
In a deploy script, add --only-missing
php artisan legal-consent:publish --all --only-missing --editorial
The idempotence above holds for unchanged sources. Once you edit a legal text and bump its
version, the next --all --editorial is not a no-op — it publishes, and it files the change as
editorial, the one mode that notifies nobody. That is a classification your deploy script has no
business making.
--only-missing fills gaps and never reads a combination that already has an active version, so it
cannot classify a change at all. Keep the bare --all for a person who has looked at the diff and
chosen the mode on purpose. Use --dry-run with either to see what a run would do before it does
it.
A registered document with no source is reported, not skipped, and the command exits non-zero —
and that holds under --only-missing too, for every source except one waiting on an author. A
missing markdown file is a deployment missing a file, not a text somebody still has to write.
legal-consent:doctor names the same gap at any time, without changing anything.
Next
Continue with the quick start: write a text, publish a version, give a model a ledger, and switch the gate on.