nowo-tech/cookie-consent-bundle
A PHP package for adding customizable cookie consent banners and privacy preference controls to web applications. Configure consent categories, manage scripts, and support compliance with a lightweight, integration-friendly setup.
Read this first: it's a Symfony bundle, not a Laravel package. nowo-tech/cookie-consent-bundle ships no service provider, no config/cookie-consent.php, no Blade views, and nothing to php artisan vendor:publish. If your target app is Laravel, treat this repo as a reference implementation of the consent UX + logging model + admin settings surface and port the parts you need rather than expecting composer require to work. The rest of this section assumes you're evaluating it inside a Symfony app (or a Laravel app that already runs a Symfony-based component, e.g. via a bridge).
Minimal integration in Symfony:
composer require nowo-tech/cookie-consent-bundle — check require.php in composer.json against your runtime; the demos target PHP 8.5 but the constraint, not the demo, is what governs you.NowoCookieConsentBundle::class => ['all' => true] in config/bundles.phpconfig/routes.yaml — the consent submission endpoint is a route, not just a frontend concern:
nowo_cookie_consent:
resource: '@NowoCookieConsentBundle/Resources/config/routes.yaml'
config/packages/nowo_cookie_consent.yaml with your categories and the logging toggleResources/public)Where to look first, in order:
README.md — quickstart and the advertised feature setUPGRADING.md — the bundle now ships real upgrade notes per minor version; read the section for your version before writing any integration code, since extension-point rules changed in 1.19DependencyInjection/Configuration.php — this is the actual source of truth for config keys; the README driftsResources/views/ — note the template path and any overridable blocks, since you will almost certainly restyle itController/ — the endpoint that persists consent, and how it validates CSRF and payload shape. Note there is also a CookieDefinitionAdminController plus settings form types: the bundle ships an admin surface for managing cookie definitions, not just a front-end modal. If you're porting to Laravel, that CRUD is worth reading as a model for your own admin screen.Entity/ + Repository/ — the log schema, if logging is enabledFirst use case worth shipping: three categories (necessary / analytics / marketing), reject-all as the default state, explicit accept, and a persisted record of the choice so you can answer "did this user consent on date X?" That's the minimum a GDPR auditor will ask for, and it exercises the whole path: template → JS state → POST → database row.
Keep categories in config, not in the template. The bundle's category list drives both the UI toggles and the shape of the logged payload. If you hardcode a fourth checkbox in an overridden Twig template, your log rows and your UI silently disagree. Add the category in nowo_cookie_consent.yaml first, then let the template loop over it.
Override the template rather than editing vendor. Symfony resolves templates/bundles/NowoCookieConsentBundle/<path> ahead of the bundle's own copy. Copy the template out, then restyle freely — you keep upgrade paths intact. This is now doubly important: most internals are annotated @final, so copy-and-own is the sanctioned path, not subclassing.
Extend only what is explicitly left open. As of 1.19 the services, controllers, form types, the Twig extension and the DI classes are @final (real final in 2.0). The documented exceptions are the bundle class itself and CookieConsentConfigFullSettingsType, which your settings form is meant to extend. If your integration plan involves subclassing a controller or decorating an arbitrary service, redesign it now — the plan is to hard-close those doors.
Gate scripts on state, not on modal dismissal. The modal closing is not consent. Expose a small reader that returns the consent object from the cookie/localStorage and have your analytics loader call it before injecting any <script>. The common failure is loading GTM in <head> and only hiding it — that's not consent, and it's the exact thing a DPO will flag.
Server-side gating for embeds. For YouTube/Google Maps iframes, check the consent cookie in Twig (via a small Twig extension or a view-model) and render a placeholder instead of the iframe when the category isn't granted. Client-side-only gating leaks requests on first paint.
Consent logging workflow. With logging on, each submission writes a row: timestamp, categories granted, and typically a session/user reference plus policy version. Add a console command that prunes rows past your stated retention period — if your privacy policy says 13 months and the table grows forever, that's an inconsistency an auditor will find. Store a policy/version string on each row so old consents stay interpretable after you add a category.
Revocation entry point. Ship a persistent "Cookie settings" link (footer is conventional) that reopens the modal pre-filled with the current choice. Consent that can't be withdrawn is non-compliant, and this is the single most commonly omitted piece.
Translations: treat en as the canonical key set. The release pipeline now fails on key parity against en, which tells you the English catalogue is the reference. If you override or add a locale, diff your keys against en in a test rather than discovering missing labels in production.
Testing. A functional test that POSTs a consent payload to the bundle's route and asserts the row persisted catches most regressions — CSRF token shape, payload key names, entity mapping. Assert the reject path too; it's the one people forget to test. The upstream project now enforces a 99%-line coverage gate; that number is not your target, but the fact that they test controller + form paths is a hint about where your own regressions will land.
Version pinning. Zero stars, zero dependents, one maintainer, and this release is largely housekeeping (visibility tightening, CI gates, demo image bumps) rather than user-facing features. Pin an exact version in composer.json and read UPGRADING.md before every bump. Vendoring the template and the small JS reader into your own code reduces your exposure to a rewrite — and since 2.0 will turn those @final annotations into real final keywords, expect a breaking release sooner than the changelog cadence suggests.
The Laravel-shaped assumptions that will bite you: no config publishing (you write the YAML by hand), no Blade (Twig), no Eloquent (Doctrine entity), no artisan commands. Budget for translation work if you port rather than adopt — and if you port, port the admin cookie-definition screen too, or you'll be editing PHP arrays every time marketing wants a new cookie documented.
@final is a roadmap, not a suggestion. If you write a subclass or decorator today that the changelog doesn't list as an extension point, it will break on 2.0. Treat any @final class you touch as already sealed: prefer template override and config, and if you must wrap something, wrap a service interface you own rather than the bundle class.
Zero dependents means zero field reports. There is no corpus of Stack Overflow answers or GitHub issues to lean on. When something misbehaves, bin/console debug:config nowo_cookie_consent is your fastest signal — it prints the merged config, which reveals whether your key was silently ignored because of a typo or an unexpected nesting level.
Config changes need a cache clear. In prod with a warmed config cache, editing the YAML appears to do nothing. bin/console cache:clear after every config edit, and remember the cached config is what ships to production.
CSRF on the consent POST. If the bundle's route sits behind your app's CSRF protection, an expired session or a stale token yields a 419-style failure that the frontend may swallow, leaving the user believing they consented when nothing was logged. Check the network tab, not just the UI, when verifying. The form-factory-via-constructor change in this release is a reminder that these form types participate in the normal Symfony form flow — CSRF and validation are live.
Version the consent cookie. If you later add a "marketing" category or change your policy text, users holding an old cookie should be re-prompted. Bake a version number into the cookie name or payload and bump it on material changes; otherwise you'll be treating pre-change consents as covering categories the user never saw.
IP addresses in the log are personal data. If the logging includes the client IP by default, truncate or hash it, or drop it — the consent log is evidence, not an analytics table. Document the retention window and make the pruning command part of your scheduled tasks.
Extension points, in rough order of safety: template override → config categories/translations → extending CookieConsentConfigFullSettingsType for settings → decorating a service you own (not a @final bundle service) → forking. Reach for the first three before the last two.
Debugging a "modal never appears": check in this order — is the template actually included in the layout that renders, are the assets 404ing (common with AssetMapper path config), and is a leftover consent cookie from testing suppressing it? Clear the cookie before concluding the bundle is broken.
Audit the JS before trusting it. With this little external scrutiny, read the frontend code once for anything that fires a network request before a choice is made. It's a short file and it's the only part that determines whether the thing is actually GDPR-compliant. The 1.19 release raised the bar on PHP-side coverage and translation parity but says nothing about front-end behaviour — a green CI badge is not a consent audit.
How can I help you explore Laravel packages today?