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 Cookie Consent Laravel Package

jeffersongoncalves/laravel-cookie-consent

A Laravel package for adding a customizable cookie consent banner to your site, helping you meet GDPR and CCPA requirements. Settings are stored in the database via spatie/laravel-settings and rendered with simple Blade includes.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and migrate — both steps are mandatory.

composer require jeffersongoncalves/laravel-cookie-consent
php artisan migrate

Unlike most banner packages, there is no config file. All 24 settings live in the database via spatie/laravel-settings (group cookie_consent), and the package auto-loads its own settings migrations from database/settings. Skip the migrate step and the banner will either not render or blow up resolving CookieConsentSettings — so this must be in your deploy script, not just your local setup.

2. Wire the two partials into your layout. Order matters — CSS in <head>, JS init at the end of <body>:

{{-- resources/views/layouts/app.blade.php --}}
<head>
    ...
    @include('cookie-consent::cookie-consent-head')
</head>
<body>
    {{ $slot }}

    @include('cookie-consent::cookie-consent-body')
</body>

That's the whole integration. If your app has multiple layouts (marketing, auth, app shell), each one needs both includes — a banner that appears on the marketing site but not in the authenticated app is an audit finding waiting to happen.

3. Where to look first. There are only four things that matter:

Path Why you care
resources/views/cookie-consent-head.blade.php CSS <link> from css_url
resources/views/cookie-consent-body.blade.php The window.cookieconsent.initialise({...}) call built from settings
src/Settings/CookieConsentSettings.php Typed properties; source of truth
src/helpers.php cookie_consent_settings() global helper

Publish the views before you start tweaking, so you have something concrete to diff against:

php artisan vendor:publish --tag=cookie-consent-views

Only publish the settings migrations (--tag=cookie-consent-settings-migrations) if you actually need to alter defaults for every environment — see Gotchas.

4. First real use case. The defaults ship a banner with a "Learn more" link whose content_href is null — i.e. a compliance banner with no path to your policy. Fix that immediately, plus the legal entity's copy, from a seeder/console command or a one-off tinker session:

$settings = cookie_consent_settings();
$settings->content_href     = route('legal.cookies');   // or '/privacy'
$settings->content_message  = 'We use cookies to analyse traffic and improve your experience.';
$settings->content_policy   = 'Cookie Policy';
$settings->position         = 'bottom-right';
$settings->popup_background = '#111827';
$settings->highlight_background = '#f8e71c';
$settings->save();

Note save() — settings are a persisted object, not a config array. Nothing changes until you call it.

Implementation Patterns

Treat settings as admin-editable data, not code

The v2 rewrite moved everything into the database specifically so settings can change at runtime. The idiomatic pattern is a settings page (Filament, Nova, plain Blade form) that does exactly what the snippet above does. Keep it out of config/ and out of .env — there is no config file to put it in anyway.

// app/Filament/Pages/CookieConsentSettingsPage.php (sketch)
protected function mutateFormDataBeforeSave(array $data): array
{
    $settings = cookie_consent_settings();
    foreach ($data as $key => $value) {
        $settings->{$key} = $value;
    }
    $settings->save();

    return $data;
}

Validate aggressively here: position and theme are passed straight into Osano's initialiser and only accept its enumerated values (top-left, top-right, bottom-left, bottom-right; block, edgeless, classic). A typo in an admin form silently produces an unstyled or mispositioned banner.

Gate your own scripts on the consent cookie

This is the single most important integration point, and the README doesn't spell it out: the package renders a banner, it does not block anything. Osano CookieConsent v3 writes a cookieconsent_status cookie with allow, deny, or dismiss. Your analytics/marketing tags must check it themselves:

<script>
  (function () {
      var ok = document.cookie.split('; ').some(function (c) {
          var [k, v] = c.split('=');
          return k === 'cookieconsent_status' && (v === 'allow' || v === 'dismiss');
      });
      if (!ok) return;

      // load GA4 / Pixel / Hotjar here, or inject the script tag
  })();
</script>

Because v3 fires no callbacks through a Laravel-friendly API, the pragmatic pattern is to hook Osano's own event and re-run your loader:

window.cookieconsent.initialise({ onStatusChange: function () { location.reload(); } });

A reload after consent is crude but bulletproof and avoids re-plumbing every tag loader. For a heavier marketing stack, push to dataLayer instead and let GTM's consent triggers do the work.

Self-host the assets for EU traffic

The defaults point at cdn.jsdelivr.net. That's a third-party request fired before the user has consented, which some DPOs object to, and it complicates a strict CSP. Override both URLs to local copies:

$settings->css_url = asset('vendor/cookieconsent/cookieconsent.min.css');
$settings->js_url  = asset('vendor/cookieconsent/cookieconsent.min.js');
$settings->save();

Copy the two files from the npm package (cookieconsent@3) into public/vendor/cookieconsent/. This also removes a runtime dependency on jsDelivr availability — your compliance banner should never be the thing that fails to load.

Full-page caching and CDN caching

Settings resolution goes through spatie's settings cache, so reading cookie_consent_settings() in a Blade view is cheap. But since the partials are rendered per-request into your layout, a full-page cache keyed only on URL will serve a stale banner config after an admin change. Either exclude pages containing the banner from page cache, or bust the cache from your settings page's save handler.

Per-environment seeding

For reproducible deploys, drive the settings from a seeder rather than clicking through an admin panel in each environment:

// database/seeders/CookieConsentSettingsSeeder.php
public function run(): void
{
    $s = cookie_consent_settings();
    $s->content_href = config('app.url') . '/legal/cookies';
    $s->content_message = __('consent.message');
    $s->position = 'bottom-right';
    $s->save();
}

Call it from your deploy script after php artisan migrate. Keep it idempotent (assign, then save()).

Multi-tenant / multi-brand

CookieConsentSettings has a single fixed group (cookie_consent), so there is exactly one banner configuration per database. If you run several brands or tenants off one app, you'll need to either extend the settings class with a dynamic group name per tenant, or resolve the copy at render time by overriding the published views to read from a tenant-aware source. Plan for this early — retrofitting it after the views are published and edited is annoying.

Gotchas and Tips

Laravel 11 is gone. v4.0.0 requires laravel/framework: ^12.61.1|^13.12.0, specifically to pull in fixes for GHSA-crmm-hgp2-wgrp and GHSA-5vg9-5847-vvmq. If you're pinned to L11 for other reasons, you're stuck on v3.x and missing those patches. Audit this before composer update surprises you mid-sprint.

Editing the settings table by hand doesn't take effect. Spatie caches the settings object. Writing SQL directly against the settings table requires:

php artisan settings:clear-cache

Always prefer $settings->save() from code. This is the #1 source of "I changed it but the banner is the same" confusion.

Publishing views is a one-way door for that file. Once cookie-consent-views is published, package updates to those Blade files stop reaching you. Pin the package major version when you publish, and diff the upstream views on upgrade. Same for the settings migrations — if you publish them, later versions that add properties (v3.0.0 added eight content_* fields) won't auto-migrate.

XSS surface in the content settings. content_close defaults to the raw HTML entity &#x274c; (a ❌ glyph), which strongly implies the body view emits content unescaped into the JS config. Any admin-editable content field — content_header, content_message, content_link — is therefore a stored-XSS vector if untrusted users can reach the settings page. Restrict that page to super-admins, or sanitise with strip_tags/Purifier before assigning.

content_href defaults to null. An unlinked "Learn more" is a common GDPR/CCPA finding. Set it, and also decide whether you want content_target = '_blank' (the default) — linking to your own policy in a new tab is unnecessary and hurts nothing but UX.

Nothing is recorded server-side. The package gives you a banner and a browser cookie, full stop. "Prove this user consented, and to what version of the policy" requires your own table plus a POST from the consent callback. If your compliance posture demands an audit trail, budget for that work separately — don't assume the package covers it.

No category granularity. You get dismiss / allow / deny only. There is no "necessary vs. functional vs. marketing" toggle set, and no way to re-open preferences without the user clearing cookies or you adding a "Manage cookies" button that calls window.cookieconsent.show(). If your cookie policy lists multiple categories, the banner and the policy will contradict each other. Either simplify the policy or swap to an Osano v4-style config with categories.

Position/theme values are unvalidated pass-throughs. A position of bottom (rather than bottom-left) is silently ignored by Osano and the banner sticks to its default position. Add an in: validation rule on any admin form.

Debugging a banner that won't appear:

  1. Confirm both partials are actually in the rendered HTML (view-source, not DevTools' inspector).
  2. Check settings table has a cookie_consent group row — if migrations never ran, the settings class throws on resolve.
  3. Check the browser for an existing cookieconsent_status cookie; once set, the banner is suppressed by design. Test in a private window.
  4. Check CSP script-src/style-src — the CDN defaults are the usual culprit, and the failure is silent in the console unless you're watching.
  5. Grep the body partial for the initialiser; if js_url 404s, window.cookieconsent is undefined and nothing renders.

Extension points, ranked by durability. Overriding the two published Blade views is the only real extension seam — there's no event system, no middleware, no manager class to swap. That means: keep your edits in a thin wrapper (e.g. a custom x-cookie-consent Blade component in your app that includes the package partial and layers your own JS on top) rather than heavily editing the vendor files. That way a package major bump is a 5-minute diff, not a rewrite.

Testing. The package's own suite is composer test; in your app, add a smoke test that renders a page containing the layout and asserts the banner markup is present, and a settings test that assigns + saves + re-resolves to catch cache/group regressions. Use RefreshDatabase and make sure your test suite runs the package's database/settings migrations before any view rendering happens — otherwise every layout-touching feature test fails for a non-obvious reason.

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle