eveltic/cookie-bundle
Symfony bundle for managing GDPR/AEPD-compliant user cookie consent. Configure technical, preference, analytics, and ad categories, set expiration periods, and support light, dark, or auto themes. Includes Twig helpers, Doctrine integration, ajax-driven UI, consent checking, versioning, and CLI arch...
The bundle is small, young (single release, 2 stars, no dependents), and its README is the primary documentation. Budget an hour to read vendor/eveltic/cookie-bundle after install — specifically src/DependencyInjection/Configuration.php, src/DependencyInjection/EvelticCookieExtension.php, and whatever is under config/routes.php / templates/ — before writing any config. That will tell you the real option keys, route names, and Twig namespaces the README only alludes to.
Minimal steps:
composer require eveltic/cookie-bundle
If Flex auto-registers it, skip the next step; otherwise:
// config/bundles.php
Eveltic\CookieBundle\EvelticCookieBundle::class => ['all' => true],
# config/routes/eveltic_cookie.yaml
eveltic_cookie:
resource: '@EvelticCookieBundle/config/routes.php'
# verify the exact file name in the bundle — it may be routes.yaml
php bin/console assets:install public --symlink # if the bundle ships static assets
php bin/console debug:router | grep -i cookie # confirm the AJAX endpoints are registered
php bin/console cache:clear
Then wire the banner into your layout once, not per page:
{# templates/base.html.twig — path/name must be confirmed from the bundle's templates/ dir #}
<body>
{{ include('@EvelticCookie/banner.html.twig') }}
...
</body>
If the bundle stores consent server-side (check for Doctrine entities or migrations), run:
php bin/console doctrine:schema:update --dump-sql # inspect first, never blindly --force
First use case to target: get the banner rendering, accept/reject once, and confirm the AJAX round-trip stores the decision (check the network tab + the cookie name in devtools). Everything else — category gating, retract button, dark theme — is layered on top of that working loop.
Include once, globally. Put the banner include in your base layout above your site JS. Gate category scripts on consent so the banner load order doesn't matter, but the include order does — the banner needs to be able to block before third-party scripts fire.
Gate scripts by category, not by "accepted all". The value of the bundle is granular consent. Keep a thin wrapper so you don't scatter conditionals:
{# Pseudocode — verify the exact Twig function / service name in the bundle #}
{% if cookie_consent('analytics') %}
<script async src="https://www.googletagmanager.com/gtag/js?id=..."></script>
{% endif %}
If there's no server-side accessor, expose consent via a cookie/localStorage read in a small inline script and keep the same call shape, so swapping to a service later is mechanical.
Server-side consent lookup for feature flags. If the bundle stores consent server- or cookie-side, inject the exposed service into controllers to decide whether to render analytics widgets, embeds, etc., rather than relying on JS only. This avoids FOUC and keeps content correct on first paint.
Override templates by convention. Standard Symfony bundle override path:
templates/bundles/EvelticCookieBundle/
Mirror the bundle's own template filenames. This is the intended extension point for changing copy, banner layout, or the retract button. Same approach for translations: drop files under translations/ and the bundle's existing locale keys will be overridden.
Configuration surface to use deliberately:
technical; it's the always-on bucket. Renaming categories is a breaking change for consent cookies — see Gotchas.auto only if you're okay with the banner changing appearance mid-session when the OS theme flips.Policy versioning (do this yourself). AEPD re-consent works on the "current policy" idea. Store a version string alongside the consent record (config value or constant) and re-prompt when it changes. The bundle's UI won't know your cookie policy changed — you have to bump the version.
Audit trail. "Proof of consent" for AEPD realistically means a server-side log: timestamp, categories accepted, policy version, and ideally an identifier. A browser cookie alone is client-controlled and weak evidence. If the bundle doesn't ship a consent log, add a listener/subscriber — or wrap the bundle's consent service with a decorator — that persists each decision to a table. This is the single most likely gap in a small compliance bundle; assume it's yours to fill.
Retract path. AEPD 2024 explicitly requires easy withdrawal. Place the retract button (or floating button) on the cookie policy page and, ideally, site-wide. Wire it to the same consent storage so there's one source of truth.
Firewall note. The AJAX endpoints must be reachable for anonymous users — make sure your firewall grants PUBLIC_ACCESS to the bundle's route prefix, or the banner silently fails for logged-out visitors, which is most visitors.
Pin the version. One release, 0 dependents, 2 stars. "eveltic/cookie-bundle": "1.*" in composer.json — don't let a ^ float into a changed consent API. Keep a changelog diff when upgrading and re-test the accept/reject/retract loop manually.
Read the source, not the README. Maturity is "readme + releases," so the README is the spec, and it will lag behavior on edge cases (AJAX error handling, what happens on a rejected-all state, whether technical is always implicitly accepted). grep -r "isAccepted\|consent" vendor/eveltic/cookie-bundle/src will answer questions faster than guessing.
AJAX-only design has two common failure modes:
Add a smoke test: open incognito, accept analytics, reload, confirm the third-party script fires and the consent cookie persists.
Cookie name and category mapping drift. If a category is renamed or split, existing cookies carry stale category keys and users appear "already consented" to categories that no longer exist. Bump your policy version and clear consent on the rename. Same class of problem if you change expiration values — decide explicitly whether existing consents keep their old expiry.
CSP. The banner's JS is likely inline or loaded as a bundle asset. If you enforce a Content-Security-Policy, use a nonce or hash and make sure the bundle's script tag is covered. A strict CSP will quietly break the banner and the retract button.
Theme + FOUC. auto and dark mode can cause a flash if the banner CSS loads late. Either inline the critical banner styles or set the theme class on <html> server-side if the bundle exposes a Twig helper for it.
Translations. AEPD is Spanish regulation — Spanish copy should be the expected baseline. Check translations/ in the bundle for es and en coverage, then add your locale. Missing keys usually fall back to the key identifier, which looks broken and is a compliance-copy smell.
Debugging checklist when "the banner doesn't show":
php bin/console debug:router | grep -i eveltic — routes registered?php bin/console debug:container | grep -i eveltic — services registered?sessionStorage.clear(), delete the cookie) and reload.assets:install run after upgrade? Cached template stale after override? cache:clear.Do not rely on the bundle for the legal determination. It gives you the UI and consent plumbing. Whether your analytics setup, cookie list, and policy text comply with AEPD 2024 is still your call. Keep the cookie inventory (names, purposes, expiry) in a config file you own, and treat the bundle as presentation + storage.
Template overrides are the safest customization point. Avoid forking the bundle or decorating core services unless you must; the API is likely to move. If you need custom consent persistence, prefer a decorator over a subclass so upgrades surface breakage at one boundary.
Retract button placement. Don't bury it. The floating button is the low-friction option; also expose it on the cookie policy page where users expect it. If the floating button overlaps a mobile nav, the bundle probably has a config toggle for its position — check Configuration.php before patching the template.
How can I help you explore Laravel packages today?