indigophp/cookie-consent-bundle
Symfony bundle for GDPR-friendly cookie consent. It shows a customizable consent banner, lets visitors accept or reject cookie categories, and stores their choices so analytics, tracking, and other scripts load only after permission is granted.
Reality check first: this is a Symfony Bundle, not a Laravel package. There is no service provider, no facade, no auto-discovery, no Blade views. composer require indigophp/cookie-consent-bundle will pull in symfony/framework-bundle and twig/twig transitive dependencies that your Laravel app will never instantiate. The repo is also archived — treat it as reference source, not a dependency.
Where to look first (in the GitHub repo, not in vendor/):
Resources/ — the front-end JS/CSS assets wrapping the upstream Cookie Consent plugin. This is the only artifact with day-to-day value.Configuration.php / Extension — read it as a spec for the config surface (theme, position, copy, policy URL, categories, accept/deny callbacks). Port the shape into config/cookie-consent.php; discard the Symfony plumbing.Minimal first use case (30–60 minutes):
composer remove indigophp/cookie-consent-bundle if you already added it (keep vendor/ around until you've copied assets out).Resources/public/* into resources/vendor/cookie-consent/ or, better, npm i cookieconsent (the upstream osilviotti/cookieconsent package) and import it in resources/js/app.js.php artisan vendor:publish won't work — just create config/cookie-consent.php by hand with enabled, policy_url, theme, position, categories keys.resources/views/components/cookie-consent.blade.php and include it once in layouts/app.blade.php, guarded by @if(config('cookie-consent.enabled')).DOMContentLoaded with a callback that only then loads GA4 / Meta Pixel.That's the whole "integration" — the bundle saves you maybe an hour of figuring out the JS options object.
Config-as-spec port. Lift the bundle's option tree verbatim into config/cookie-consent.php so the mental model carries over:
return [
'enabled' => env('COOKIE_CONSENT_ENABLED', true),
'policy_url' => '/privacy',
'theme' => 'light',
'position' => 'bottom',
'categories' => ['necessary', 'analytics', 'marketing'],
];
Blade component with Alpine.js for state. The bundle delegates state to the JS plugin; do the same but keep your own localStorage key so you control versioning:
<div x-data="cookieConsent()" x-show="!decided" …>
Store {version, timestamp, categories} — Legal will eventually ask for the version so you can re-prompt after a policy change.
Service-provider-only wiring. Unlike the Symfony bundle, everything goes in AppServiceProvider::boot() — config merge is unnecessary (Laravel merges config/ automatically) and asset registration is a Vite::asset() call. There are no events, no container bindings, no Twig. This is the entire reason the Symfony layer is dead weight here.
Gate scripts server-side in Blade, not just client-side. The most common failure pattern: banner shows, user denies, but the <script> tag for GA4 was already rendered in the layout. Use a directive:
@cookieConsent('analytics')
<script async src="...gtag/js"></script>
@endif
Implement it in a Blade directive registered in AppServiceProvider that reads a consent cookie set on accept. Client-side gating alone is theater — a hard refresh of page source shows the pixels.
Deferred analytics bootstrap. Rather than conditional rendering, register a JS hook fired from the accept handler:
window.dataLayer = window.dataLayer || [];
document.addEventListener('cookie-consent:accepted', ({detail}) => {
if (detail.categories.includes('analytics')) loadGA4();
});
This is the closest analogue to the bundle's accept callback and matches Consent Mode v2 patterns if you later graduate.
Layout-level invocation via a single partial. Render <x-cookie-consent /> in layouts/app.blade.php and layouts/guest.blade.php only — not in emails, PDFs, or admin layouts. The Symfony bundle's request-lifecycle integration is the analogue; a Blade include is the Laravel equivalent.
Feature-flag and geo-gate behind an abstraction now. Even if you don't need geo today, wrap the decision in a tiny service:
class ConsentBannerPolicy {
public function shouldShow(Request $r): bool {
return config('cookie-consent.enabled')
&& ! $r->cookie('cookie_consent_version') === current_version();
}
}
When Legal asks for EU-only banners, you add a GeoIP check in one place instead of hunting through Blade files.
Settings/revocation link. The bundle ships no revocation UI. Add a persistent "Cookie settings" link in your footer that clears the consent cookie and re-opens the modal — a five-line Alpine dispatch. Do this on day one; it's the requirement everyone forgets until the first DPO review.
It will not install cleanly on Laravel 10/11. Constraint resolution will fail against modern Symfony. If you must inspect it, use composer require --dev --ignore-platform-reqs in a scratch directory — do not add it to the main composer.json. composer why-not indigophp/cookie-consent-bundle before anything else.
The PHP layer is inert in a Laravel context. No registerBundles(), no bundle Extension, no PackageManifest entry that does anything. Copying vendor/ into your app and expecting behaviour is a common first stumble — only Resources/public/ matters.
Upstream Cookie Consent is deprecated. The bundle wraps Silktide/Insites Cookie Consent v3, which has been superseded. The modern replacement is orestbida/vanilla-cookieconsent (MIT, actively maintained, granular per-category, Consent Mode v2 support). If you're doing the pattern anyway, use the maintained JS and skip the bundle's fork entirely.
Twig leakage. Adding the bundle without removing it puts twig/twig in your composer.lock. It won't do anything, but it's a runtime dependency, an audit finding, and noise in security scans. Remove the requirement after extraction.
Don't trust a banner for compliance. The bundle delivers notice only. It does not give you: proof-of-consent records, per-category opt-in state persisted server-side, revocation API, geo-targeting, or Google Consent Mode v2. For a Laravel app with EU traffic, the pattern is: banner (UI) + Blade directive (blocking) + consent record table (audit) + revocation link (settings). The bundle covers the first; build the other three.
Cookie name and version collisions. If you copy the bundle's default cookie name, and later migrate to vanilla-cookieconsent, existing users will have stale preferences that don't match your new category schema. Namespace it from the start (yourco_consent_v1) and bump on every policy change.
CSP and asset delivery. The bundle's inline <style> in the Twig template will break a strict style-src policy. When you port to Blade, publish the CSS as a Vite entry rather than inlining. Same for the JS init — move it to your bundler entry so a nonce or hash isn't needed.
Debug tip: in config/cookie-consent.php, gate enabled on ! app()->isProduction() or a COOKIE_CONSENT_ENABLED env var so CI/QA doesn't see a banner on every screenshot test, but staging matches prod behaviour.
Extension point you'll actually use: the accept callback. Wrap it in a Laravel-idiomatic event (ConsentGranted) so analytics, marketing pixels, chat widgets, and future integrations all subscribe independently. The bundle's single JS callback would otherwise become an unmaintainable switch statement within a quarter.
Bottom line for the ticket: do not add this to composer.json. Read it as a reference for a 1–2 day in-house Blade component plus a service provider, and keep the CMP buy/no-buy decision as a separate v2 conversation owned by Legal and Product.
How can I help you explore Laravel packages today?