Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Consent Laravel Package

netipar/laravel-consent

A Laravel package for managing user consent and privacy preferences. Track opt-ins and opt-outs, store consent records, and integrate GDPR-friendly consent flows into your application with simple, configurable APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a full-stack consent package: a typed Laravel config, a server-side Consent facade, an unstyled Blade banner, and three JS adapters (vanilla, Alpine, Vue 3) that all read the same clientConfig(). The upgrade path from netipar/cookie-consent 1.x is documented, so treat v0.1.0 as a rename/rewrite rather than a patch — plan a small migration rather than a composer update.

Install and publish in this order:

composer require netipar/laravel-consent

php artisan vendor:publish --tag=consent-config     # config/consent.php
php artisan vendor:publish --tag=consent-lang       # de, en, es, fr, hu
php artisan vendor:publish --tag=consent-migrations # only if you enable logging
php artisan migrate

Look here first: config/consent.php. It is validated by a ConsentConfig object, so a typo throws at boot instead of silently disabling gating — that is the fastest feedback loop you have. Confirm version, categories, cookie.domain, policy_url, and log.enabled before touching any Blade.

First use case — gate a third-party script and render a banner in one pass:

{{-- layouts/app.blade.php --}}
@consentConfig
<x-consent::banner policy-url="{{ Consent::policyUrl() }}" />

<script type="text/plain" data-consent="analytics" src="https://analytics.example/script.js"></script>
// anywhere server-side
if (Consent::allows('analytics')) {
    $this->dispatchAnalytics();
}

The type="text/plain" + data-consent pattern is the core primitive — the JS core (activateGatedScripts) rewrites the script tag once the category is granted. Everything else (banner, store, Google mode) builds on that.

Implementation Patterns

Pick one adapter, share one config. Consent::clientConfig() is the single source of truth: categories, texts, cookie attributes, Google settings. Blade, Alpine and Vue all consume it. Do not re-declare categories in JS — read them from the store/plugin.

// Alpine, in app.js
import Alpine from 'alpinejs'
import { registerConsent } from '@netipar/consent-alpine'
registerConsent(Alpine)
Alpine.start()
{{-- inside the banner's default slot --}}
<button @click="acceptAll">Accept all</button>
<button @click="reject">Reject</button>

Two-way gating. Client-side gating is for loading scripts; server-side Consent::allows() is for deciding what to render or record. Use both, but never use the cookie value in JS as an authorization boundary — it is user-editable.

@consent('marketing')
    <x-marketing-pixel />
@endconsent

@unlessconsent('marketing')
    <p>Marketing cookies are currently off.</p>
@endunlessconsent

Banner slot structure. The component ships unstyled with a default slot and a settings slot (per-category toggles), plus title, description, policy-url, policy-label. Keep the accessibility contract intact: don't remove the settings slot and don't override the focus handling with your own modal trap.

Preference centre as a second entry point. Users who already decided need a way back in. Expose a persistent "Cookie settings" link that reopens the banner — the store/consentBanner component is designed for this, so wire it to a footer link rather than building a separate settings page.

Google Consent Mode v2. Configure google.tags, google.signals, ads_data_redaction, url_passthrough in config, then let the package handle ordering:

'google' => [
    'tags' => ['G-XXXXXXX'],
    'signals' => ['analytics_storage', 'ad_storage', 'ad_user_data', 'ad_personalization'],
    'ads_data_redaction' => true,
    'url_passthrough' => true,
],

Tags load only after consent; later grants send consent update and a late config. Do not also drop your own gtag('consent', 'default', ...) snippet in the head — you will race the package's defaults.

Logging and audit trail. Enable log.enabled / CONSENT_LOG_ENABLED only if you need proof of consent. The endpoint is POST /consent/log behind web middleware, so the browser needs a CSRF token; throttled, and idempotent via event_id. Then:

Consent::consentId();   // stable visitor-scoped id
Consent::history();     // prior decisions for this consent id

Schedule pruning, because retention_days is enforced by the model, not by a cron baked into the package:

// routes/console.php
Schedule::command('model:prune')->daily();

Cookie table for the policy page. Consent::cookieTable() flattens the rich cookies entries (provider, translatable purpose, ISO 8601 lifetime). Render it with <x-consent::cookie-table /> or Vue's ConsentCookieTable — no need to maintain a second, drifting list of cookie names in your privacy copy.

Locale-aware policy links. policy_url accepts a string, a [locale => url] map, or a route name. With a map, Consent::policyUrl() resolves per request, which is what you want on a mcamara/laravel-localization site. With a route name, your route must exist in every locale, otherwise resolution is the thing that breaks first in staging.

Livewire / SPA navigation. The Alpine adapter is Livewire-navigation aware; the Vue plugin handles its own router. If you mix both, mount only one. Re-registering consent state on every wire:navigate is handled for you — don't add your own livewire:navigated listener that re-runs activateGatedScripts, or scripts will double-execute.

Server-rendered pages and caching. clientConfig()['texts'] is resolved per request. If you cache rendered HTML containing the banner or @consentConfig, vary the cache on the consent cookie and Accept-Language, or you will serve Hungarian buttons to English visitors and a stale grant state to everyone.

Upgrade from cookie-consent 1.x. Follow the upgrade guide rather than the README: the cookie contract changed to __cookie_consent + __cookie_consent_v and the version check is now enforced. Expect all existing visitors to be re-prompted once after deploy.

Gotchas and Tips

  • version invalidates everything. A decision is only valid for the current config version. Bump it when categories or vendors change (that is the point), but know that every bump re-prompts every visitor and, via the stale-version path, runs the cookie deletion sweep. Don't bump it in a hotfix without saying so.
  • The cookie is read from the raw Cookie header. That means request()->cookie() and CookieJar will not see __cookie_consent, and any middleware that encrypts or rewrites cookies can break parsing. If you have custom cookie middleware, test consent detection with it enabled.
  • Withdrawal deletes cookies on the host and parent domains. If cookie.domain doesn't match how your other cookies were actually set (e.g. .example.com vs example.com), the deletion silently no-ops and revoked analytics cookies survive. Verify in devtools, not in tests — the browser is the only honest witness.
  • _ga_* prefix matching is a feature, not a wildcard you control. Per-category cookies lists support exact names and prefixes; a mis-typed prefix deletes nothing and reports nothing. Log the list once at boot while you're wiring it.
  • The log endpoint needs CSRF and throttling headroom. A user clicking through the banner on a shared IP can hit the throttle. The event_id idempotency protects you from duplicate rows on retry — send the same id, don't generate a fresh one.
  • IP hashing is HMAC with your app key. Rotating APP_KEY invalidates correlation with older log rows. If you need long-lived audit data, record that constraint somewhere durable.
  • reload_on_revoke is a blunt instrument. It guarantees clean teardown of already-running scripts, but it will also discard in-progress form state. Enable it only where third-party scripts cannot be unloaded otherwise.
  • @consentConfig emits JSON into the page. It is convenient and easy to double-emit — once in the layout, once in the component's own partial. Also confirm the output is CSP-compatible in your setup (nonce support exists for gated scripts; check the config directive separately).
  • Banner ships unstyled. No CSS is included. Budget styling time and keep the shared accessibility contract: the banner must be reachable, dismissible by keyboard, and the settings slot must remain operable.
  • Texts come from lang/{de,en,es,fr,hu} and are resolved per request. Adding a locale means publishing consent-lang and overriding, not editing vendor files. Missing keys surface as the fallback locale, not as an error — check Consent::texts() for the locale you think you're serving.
  • Extension points: ConsentConfig for config shape, the clientConfig() payload for adapter-level customisation, consent:change / consent:log-failed events for analytics-of-the-banner and for alerting when logging fails, and ConsentLog as a normal prunable Eloquent model you can query or export. The bundled Laravel Boost skill consent-development and the examples/ cookbook are worth reading before you design a custom flow.
  • Debugging order when gating "doesn't work": (1) does Consent::clientConfig() contain the category, (2) is the cookie version equal to config('consent.version'), (3) did the script tag keep type="text/plain", (4) did your CSP nonce get applied to the activated tag. Those four cover almost every report.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
emissaoja/emissaoja
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
c-point-labs/laravel-hostyourai
cgv/localized-resource
noith/settings
kda/laravel-package