Skip to main content

Configuration reference

Everything lives in config/legal-consent.php. The keys you are most likely to touch:

KeyDefaultWhat it does
documentsexample terms / privacy / newsletter / imprint registry (ships pre-populated — edit it, don't author it from scratch; imprint is the informational one)The registry: each key maps to its legal_basis (contract / acknowledgement / consent / informational) and source. The quick start works precisely because terms is already registered here. Every registered document except an informational one appears on the registration form — a document that asks something has to be asked before an account exists. Per document, 'ask_at_registration' => false takes it off the form (rules, checklist and recorder together) without changing anything else: a mandatory document still gates, so the subject meets it at the re-consent screen instead. For a page that binds nobody — an Impressum, a cookie policy — use informational rather than this flag; that one leaves the gate and the notice sweeps as well.
default_locale · localesde · [de, en]The primary locale and the allowed set (publishing an unlisted locale is refused).
fallback_localedeThe recording side only: when a document is not published in the locale a consent is being recorded in, the recorder resolves this one instead of failing, and the ledger row carries the fallback's locale. The registration surface uses the same chain — requested locale, then this one, then default_locale — to show a mandatory document a visitor's language does not have. It does not apply to the read path: Consent::published() has no locale fallback on purpose, because a page must show the text of the locale it claims to be showing or nothing at all. The one exception there is an informational page, which falls back to default_locale and to nothing else. See Link the full text with the item's own locale.
retention_after_end3 yearsHow long proof is kept before legal-consent:prune removes superseded or orphaned records. Enforced only once schedule.prune is on.
cache.store · cache.ttlapp default · 86400Where and how long rendered documents are cached (self-invalidates on a content change). cache.store also decides where the activation lock lives, and that is the load-bearing half: publishing and releasing serialize on it so one document cannot end with two active versions. A store that cannot lock across processes — null, array, or anything without a LockProvider — leaves that invariant to the database alone. Since 0.24.0 the database enforces it on all three engines — a partial unique index on PostgreSQL and SQLite, a generated column with a unique index on MySQL — so a lock-less store degrades a lost race from a silent second active version into an orderly constraint violation. The package detects a store that cannot lock and logs a warning rather than refusing, so check your logs for cannot serialize if you run more than one web or queue process.
cache.prefixlegal:docThe key prefix every cached document and enforceable set is written under. Change it when two applications share one cache store and must not read each other's documents — the store itself is the boundary, and a shared Redis with one prefix has none. legal-consent:cache-flush reads the same value, so a changed prefix does not strand the old keys behind a flush that no longer names them.
cache.enforceable_ttl60How long (seconds) the gate caches the set of currently-enforceable versions — it bounds how late a scheduled enforce_from boundary or an out-of-band deactivation begins gating. A publish flushes it immediately; an out-of-band is_active write needs legal-consent:cache-flush.
change_items.requiredfalseMake a per-locale change description a precondition of releasing a change that owes a notice. Off by default — see Saying what changed.
document_urlnullWhere a document is readable: fn (LegalDocument $document): ?string, or an invokable class-string (a closure blocks config:cache). Set it and every surface that shows a document links its title to the full text — the registration checkboxes, the re-consent gate, and "Your consents". Left null, titles render as plain text, which is what every release before 0.13 did. Worth setting: consent has to be informed (Art. 7(1)), the request intelligible (Art. 7(2)), and the terms retrievable before agreeing (§ 305 Abs. 2 BGB) — and the gate is the sharpest case, because there the subject cannot continue until they agree. Resolve the URL from the document's locale, not the page's: the resolver is handed the model for exactly that reason.
notice_mailshell on, everything else nullThe seams on the three change notices. view is the package's own Markdown shell (set it to null for Laravel's global template); theme takes a Laravel theme name or a ::-namespaced view — the package ships legal-consent::mail.theme, publishable under legal-consent-mail. identity.declarant / postal_address name the declaring person (§ 126b BGB) and are appended to the notice and therefore to the proof row; leave them null and nothing is added. from, reply_to, subject_prefix and subject_effective_date are transport and framing. notification.{mode} swaps a mode's notification for your own subclass of ChangeNotification; a value that is not one is ignored in favor of the shipped class rather than killing a queued sweep halfway through. Every seam except the shell is inert by default, because the notice body is hashed into an append-only row: a seam that changed the mail unasked would move bytes nobody can correct afterwards. Multi-tenant? Bind ResolvesNoticeIdentity instead of using the static block — one global declarant names the wrong legal person in every tenant but one.
notifications.channels[mail, database]Channels for the change notifications.
notifications.max_recipients_per_runnullHolds back any version whose audience exceeds it — without stamping the watermark, so the notice is still owed on the next run. Release with dispatch-notices --force. null means no brake, deliberately: a limit that were on by default would withhold a legally required notice from an installation that never asked for one.
notice_periodsper regimeAdvance-notice days per regime and mode, and every key is read. The mode contributes active_reconsent_min_days or deemed_consent_min_days; the regime contributes psd2_min_days (floor 60), p2b_standstill_days (floor 15), eecc_min_days (floor 30) or privacy_advance_days (no floor — WP260's "well in advance" is guidance, not a number). The two are combined with max(), not chosen between: a deemed-consent change under P2B owes both its § 308 Nr. 5 benchmark and the Art. 3 standstill. An info-only change has no mode benchmark, so its regime is its only source — before 0.13 it had no advance check at all. A silent editorial change owes no notice, and declaring a regime on one is refused rather than quietly ignored. A scheduled gating change is measured announcement → effective date; a deemed-consent change is measured announcement → objection deadline (the subject must have the full period to object) and is never exempt. Too short a period is refused. A per-document min_lead_days in documents may raise a period, but never undercut a statutory floor: § 675g's two months is hard — neither an override nor this config can talk it down.
durable_mediumproof: true, channels: [mail]Whether the notice dispatch writes an append-only legal_notices proof row, and on which durable-medium channels.
schedule.dispatch_notices · schedule.close_objection_windowstrue · trueWhether the two notice sweeps are auto-registered on the scheduler (hourly).
schedule.prunefalseWhether the retention sweep is auto-registered (daily). Off by default because it deletes — see Retention.
middleware.allowlist_routes · middleware.allowlist_paths[] · []Extra route names, and extra URI paths (wildcards allowed, for example billing/*), the gate never blocks. The consent route, logout and Livewire's own endpoints are always allowed — you never need to list the latter. Livewire 4 derives its endpoint prefix from APP_KEY, so the gate resolves it from your installation at request time; writing your own /livewire-<hash>/* into this list is worse than listing nothing, because that hash is green in development and wrong in production.
gate.subject_filternullWhich authenticated subjects the gate blocks. Null gates all; a fn (Model): bool (or invokable class-string) returning false lets a subject through, so the gate can sit after your own verification or onboarding gates. A closure blocks config:cache — use a class-string in production. A misconfigured value fails safe (gated).
gate.first_usefalseAlso stop a subject who owes a first acceptance of a mandatory document, not only one who owes a re-consent. outstanding() filters on the notice mode of a version change, so a subject who never accepted anything is not in it — an application whose sign-up carries no consent step (OAuth-only, an imported user base) serves those people as though they had accepted. Read strictly: anything but a literal true leaves it off, because a gate that switched itself on for a truthy value would stop everybody and read as an outage. Turn it on together with a first-use screen — mount <livewire:legal-consent.reconsent-form :method="\Pushery\LegalConsent\Enums\ConsentMethod::FirstUseGate" /> on your consent route. Without one it is a dead end, not a loop: the consent route is allowlisted, so the subject lands there and is told nothing is due. legal-consent:doctor reports the combination.
routes.consent_name · routes.consent_pathlegal.consent · /legal-consentWhere to send a subject to act: consent_name is a route name (preferred — it survives a path change), consent_path the fallback URL. Used by the middleware redirect and by the link in every change notice, so a wrong value points the legally-required notice at a dead URL.
routes.return_to_intended · routes.homefalse · /Opt-in: after a re-consent gate is fully cleared, return the subject to the URL the middleware intercepted (stashed automatically), falling back to home. Off keeps the in-place confirmation; a settings-page embed never redirects.
routes.api · routes.api_prefix · routes.api_middlewarefalse · legal · [api, auth]Whether the headless JSON API is registered, under which prefix, behind which middleware.
routes.api_throttle60,1The rate limit the package puts in front of the four API write endpoints itself. Since Laravel 11 the api group carries a limiter only if the application called throttleApi(), and behind these routes sits an append-only ledger. Passed straight to the throttle middleware: '120,1', or the name of a limiter you registered; null takes it off.
routes.web · routes.web_prefix · routes.web_middlewarefalse · legal · [web, auth]Whether the one session-backed route — POST {web_prefix}/consent/withdraw — is registered. It is what the framework-agnostic settings stub's withdraw button posts to; with it off, the stub renders no withdraw form. Redirects back with a flashed legal-consent.status, which the JSON API's 204 cannot do.
routes.web_throttle60,1The same limit for the session-backed writes: the web withdraw route and the actions of the bundled Livewire components (grant, withdraw, object, terminate). They append to the same ledger, and a Livewire request is one POST with a CSRF token. One budget for the whole surface, keyed on the authenticated subject and kept apart from any throttle: your own routes apply. Same shape as api_throttle; null takes it off.
registration.without_form_fieldswarnWhat happens when a mandatory document is about to be recorded from a request carrying no legal_<key> field — an external-provider sign-in raising Registered with no form behind it. warn logs and records; refuse raises UnevidencedConsentException and records nothing at all. The default is lenient because the check can only look for the field name the package generates, and an application with its own form naming fields differently is correct and would otherwise start failing.
ui.variantautoWhich bundled view set is served: auto picks the WireKit-native views when pushery/wirekit ≥ 2.26.0 is installed, plain and wirekit pin the choice. A view published into resources/views/vendor/legal-consent still wins over both.
markdown.html_input · markdown.allow_unsafe_links · markdown.max_nesting_levelstrip · false · 20CommonMark hardening for rendering document sources. Loosen html_input only for sources you fully control — a legal text is rendered into your users' browsers.
admin.abilitynullThe Gate ability that unlocks the admin screens. Null means both screens return 404. See Managing legal texts.
registration.listen_to_registered_eventtrueWhether the Registered event listener records registration consent. See Recording consent.
tamper_evidence · tamper_evidence_keyfalse · unsetThe append-only hash chain and its optional HMAC key. See Optional features.
age_gateenabled: false, threshold: 16The Art. 8 attestation on registration. See Optional features.
tenancyenabled: falsePer-tenant scoping of documents and consents. See Optional features.