
Legal Consent for Laravel
Court-proof, versioned legal consent for Laravel. It is a document-acceptance ledger: the package renders and proves your legal texts — it does not own them.
composer require pushery/legal-consent-for-laravel
Why this exists
A registration does three legally distinct things, and treating them as one (a single "I accept everything" checkbox) is a common — and real — GDPR violation:
| Document | Legal basis | UI | Blocking? | Withdrawable? |
|---|---|---|---|---|
| Terms / contract | Art. 6(1)(b) — contract | "I accept …" checkbox | yes | no (you cancel, not withdraw) |
| Privacy notice | Art. 13/14 — information | "I have read …" — never "I consent" | takes notice | n/a |
| Marketing / analytics | Art. 6(1)(a) — consent | separate, granular opt-in | no (Art. 7(4)) | yes, any time (Art. 7(3)) |
This package keeps them separate by design, and proves acceptance the way the law requires (Art. 7(1); EDPB 05/2020 §108): it stores the exact text a user was shown, its version and hash, and the server-side context — not just a timestamp.
What you get
- Append-only audit ledger — every acceptance, acknowledgement, and withdrawal is one immutable row with denormalized proof (hardened by a database trigger on PostgreSQL and MySQL, and an app-layer guard everywhere).
- Versioned documents — a SHA-256 hash detects a change; you classify how it must be communicated (its notice mode); only a core contract change forces active re-consent.
- Four notice modes — an editorial change is silent; an info-only change (a § 675g payment-contract update, a material privacy notice) is announced but never blocks; a minor contract change can bind by silence with an objection window (Zustimmungsfiktion, § 308 Nr. 5 BGB); only a material core change gates. The three that announce carry durable-medium delivery proof; the editorial one announces nothing, so there is nothing to prove.
- Interchangeable content sources — Markdown files (default), or an admin-maintained draft store, edited and reviewed in your own screens before a publish freezes it.
- Fortify-optional — three of the four ways to record consent replace Fortify's own flow
(a trait, an event listener, or a headless JSON API).
laravel/fortifyis never required. - Optional, off by default — a tamper-evidence hash chain, an Art. 8 age gate, and multi-tenancy scoping — each a single config switch.
- Optional reactive UI — plain Blade stubs by default; opt-in Livewire components and a WireKit-flavored variant when you want them (no hard Livewire or Flux dependency).
Where to start
- Installation — requirements,
composer require, the publish tags, and which two groups stay deliberately separate because publishing them unasked would destroy data. - Quick start — write a text, publish a version, give a model a ledger, and switch the re-consent gate on. Four steps.
The guide
Read in order, or jump to what you need.
- Recording consent — the four ways to write the ledger, the registration checklist, the accept-time content-hash guard, and the headless JSON API.
- Objections and terminations — the two rights that need their own record, and the events your app has to act on.
- Notice modes — how a change to a legal text is classified, announced, and (only sometimes) enforced. Includes a worked example for each of the three modes that announce, and the delivery proof that a notice actually went out.
- Content sources — Markdown files, the draft store, or your own class; and how to interpolate operator identity without breaking the proof.
- Managing legal texts — the opt-in admin screens, why a published row is frozen, and how to render the public legal page from the frozen bytes.
- User interface — plain Blade stubs, Livewire components, the WireKit-native variant, the bundled translations, and the anti-dark-pattern rules baked in.
- Optional features — tamper evidence, the Art. 8 age gate, and multi-tenancy.
- Retention — the one scheduled sweep the package does not register for you, and why.
- Testing your integration —
Consent::fake(), so your application's tests exercise consent screens, the gate and the register form without migrating this package's tables into your test schema.
Reference
- Configuration reference — every key you are likely to touch, its default, and what it decides.
- Command reference — every Artisan command, the
publishflags, and the rule that a major version bump forces its own notice mode. - Upgrading — the canonical upgrade guide and the backward-compatible 0.2.x → 0.3.0 bump.