- Table prefix
- Locale detection
- Translations
- Database configuration
- Cookie inventory
- Granular cookie selection
- Page interaction overlay
- Preferences bubble
- Config API (GET)
- Admin Web UI
- Admin security
- Twig overrides
- Twig helpers
- Cold start / Site Backup
Extension alias: nowo_cookie_consent
# config/packages/nowo_cookie_consent.yaml
nowo_cookie_consent:
doctrine:
table_prefix: ''
# Deprecated: use doctrine.table_prefix instead
# table_prefix: ''
# Categories shown in the modal (required is always shown)
categories:
- analytics
- marketing
- preferences
# Persist user choices to CookieConsentLog entity
use_logger: true
# Load modal texts and display settings from Doctrine entities
use_database_config: false
# Show cookie definitions (name, category, duration, provider, purpose) in modal and legal pages
use_cookie_inventory: false
# Floating cookie icon button to reopen preferences (any corner)
preferences_bubble_enabled: false
preferences_bubble_position: bottom-right # bottom-left | top-right | top-left
# preferences_bubble_border_color: null # hex, e.g. #30363c — bubble outline and SVG icon color
# preferences_bubble_icon: null # custom HTML/SVG; empty = default cookie SVG
# Fetch modal settings from GET /cookie-consent/config in the browser
fetch_config_via_api: false
# HttpOnly flag on consent cookies
http_only: true
# Optional route name used as form action
form_action: null
# Keep enabled. Modal JS double-submits SameOrigin CSRF for XHR (no native submit).
csrf_protection: true
# Routes where the modal must not auto-open
disabled_routes:
- privacy
- imprint
# Route name patterns where the consent fragment is not rendered at all
# (empty response; no Doctrine inventory/config work). Wildcards via fnmatch.
# skip_render_routes:
# - staff_*
# - admin_*
# When non-empty: render ONLY on these public route patterns (whitelist)
# render_routes:
# - home
# - home_locale
# - guest_*
# - legal_*
# - nowo_auth_kit_*
# - health_*
# - nowo_pwa_*
# Locale detection for the consent modal
default_locale: en
enabled_locales:
- en
- es
- it
- fr
- de
- pt
- nl
- pl
- ca
ui_theme: bootstrap # bootstrap (default) or tailwind
detect_locale_from_accept_language: true
# Full-page overlay and scroll lock until the user chooses an option
disable_page_interaction: falseWhen doctrine.table_prefix is set to e.g. app_, the log table becomes app_dashboard_cookie_log. Useful when multiple apps share a database or when your project uses a global table prefix convention (same pattern as DashboardMenuBundle).
Root table_prefix is deprecated but still supported as a fallback.
The bundle resolves the modal locale automatically in this order:
localequery parameter_localeorlocalerequest attribute- Main request locale (for ESI/sub-requests)
- Current request locale
Accept-Languageheader (whendetect_locale_from_accept_languageis true)default_locale
Configure supported languages with enabled_locales. Bundle translations ship for en, es, it, fr, de, pt, nl, pl, and ca.
Choose the markup framework for the bundled consent modal:
nowo_cookie_consent:
ui_theme: bootstrap # default
# ui_theme: tailwindbootstrap— Bootstrap 5 modal markup (load Bootstrap CSS/JS in your layout, or rely on the CSS fallback innowo-consent-modal.js).tailwind— Tailwind utility classes; load Tailwind in your layout (CDN or build). No Bootstrap required.
By default nowo-consent-modal.js injects cookie-consent.css as a <style> tag. Hosts with CSP style nonces should link the standalone file instead so styles are not dropped:
<link rel="stylesheet"
href="{{ asset('nowo-cookie-consent.css', 'nowo_cookie_consent') }}"
data-nowo-cookie-consent-css>Markers that skip injection: link[data-nowo-cookie-consent-css], html[data-nowo-cookie-consent-external-css="true"], or #cookieconsent[data-nowo-external-css="true"].
When use_database_config: true, modal layout / position / equal-weight buttons live on CookieConsentConfig (admin Profile settings), not YAML. Recommended defaults (new entity rows from 1.9.0):
| Option | Recommended |
|---|---|
| Consent modal | box + wide + bottom + left + equal-weight buttons |
| Preferences modal | same corner + equal-weight buttons |
Override --nowo-cc-* tokens in host CSS to match brand colors.
Override templates per theme:
| Theme | Modal | Form theme |
|---|---|---|
| bootstrap | templates/bundles/NowoCookieConsentBundle/cookie_consent.html.twig |
form/cookie_consent_theme.html.twig |
| tailwind | templates/bundles/NowoCookieConsentBundle/cookie_consent.tailwind.html.twig |
form/cookie_consent_theme.tailwind.html.twig |
Twig helpers:
nowo_cookie_consent_enabled_locales()nowo_cookie_consent_locale()
Translation domain: NowoCookieConsentBundle (CamelCase, matching the bundle name).
The bundle ships YAML files for en, es, it, fr, de, pt, nl, pl, and ca under src/Resources/translations/. Symfony loads the app translations first; missing keys fall back to the bundle.
- Use the same domain:
NowoCookieConsentBundle. - Create a file in your application:
translations/NowoCookieConsentBundle.<locale>.yaml(or.xlfif your project uses XLF).
- Override only the keys you need. Keys not defined in the app file use the bundle default.
Example — override for the Spanish locale file (English sample values; replace with your locale copy):
# translations/NowoCookieConsentBundle.es.yaml
nowo_cookie_consent:
consent_modal:
title: 'Cookie settings'
description: 'We use cookies to improve your experience.'
category:
analytics: 'Analytics'- Clear the Symfony cache in dev if translations do not appear:
php bin/console cache:clear.
Set nowo_cookie_consent.privacy_route to a Symfony route name to link the privacy policy from the modal.
When use_database_config: true, per-locale copy can also be stored in CookieConsentConfigTranslation entities (admin CRUD); YAML overrides still apply to keys not overridden in the database layer.
When use_database_config is true, the bundle loads copy and display settings from Doctrine entities:
CookieConsentConfig— behavior and modal layout (one default enabled record is typical)CookieConsentConfigTranslation— per-locale texts mapped to bundle translation keys
Tables: dashboard_cookie_config and dashboard_cookie_config_translation (with optional doctrine.table_prefix).
The Symfony 8 demo enables this option and provides an admin CRUD at /demo/admin/cookie-consent-config.
Enable the full-page overlay and scroll lock per consent profile:
- Set
use_database_config: trueinconfig/packages/nowo_cookie_consent.yaml. - Open profile settings in the admin UI:
- Bundle admin (import bundle routes):
/cookie-consent-config/{id}/settingsredirects to/settings/profile— routesnowo_cookie_consent_config_settings_edit(redirect) andnowo_cookie_consent_config_settings_section(section form) - Section slugs:
profile,behavior,appearance,consent-modal,preferences-modal,route-targeting - Symfony 8 demo: Admin → Cookie consent config → profile → Settings
- Bundle admin (import bundle routes):
- Open the Appearance tab, enable Disable page interaction (
disablePageInteraction). - Adjust overlay intensity with Color theme on the same screen (
--nowo-cc-overlayin CSS).
The bundle ships one FormType per settings tab under Nowo\CookieConsentBundle\Form\Settings\ (resolved via CookieConsentConfigSettingsSection::formType()), CookieConsentConfigSettingsAdminController, area/section tab partials, and Bootstrap templates under @NowoCookieConsentBundle/admin/config/. Wire routes via @NowoCookieConsentBundle/Resources/config/routing.yaml (same as cookie inventory admin).
The database value overrides the global YAML default when both are set. Twig helper: nowo_cookie_consent_disable_page_interaction().
When use_cookie_inventory is true, the bundle exposes structured cookie definitions in the preferences modal (per category) and via nowo_cookie_consent_cookie_inventory().
Each entry includes:
| Field | Description |
|---|---|
name |
Cookie name (e.g. _ga, PHPSESSID) |
category |
Consent block / category (required, analytics, marketing, …) |
duration |
Retention period (free text, e.g. Session, 2 years) |
type |
first_party or third_party |
provider |
Vendor or domain (translatable) |
purpose |
GDPR purpose text (translatable) |
allowed_by_default |
Pre-check in granular mode before consent is saved (YAML: allowed_by_default; DB column) |
When use_database_config is enabled, store definitions as Doctrine entities linked to each profile:
CookieDefinition—name,duration,category,type,sort_order,allowed_by_defaultCookieDefinitionTranslation— per-localeproviderandpurpose
Tables (with optional prefix):
{prefix}dashboard_cookie_definition{prefix}dashboard_cookie_definition_translation
Run doctrine:schema:update or add a migration after enabling the bundle entities. The demo seeds sample rows with app:seed-cookie-definitions.
When the active profile has no database rows, you can declare cookies in configuration:
nowo_cookie_consent:
use_cookie_inventory: true
cookie_inventory:
- name: PHPSESSID
duration: Session
category: required
type: first_party
sort_order: 0
translations:
en:
provider: This website
purpose: Keeps your session active.
es:
provider: Este sitio
purpose: Keeps your session active.
- name: _ga
duration: 2 years
category: analytics
type: third_party
sort_order: 10
allowed_by_default: false
translations:
en:
provider: Google Analytics
purpose: Distinguishes users for statistics.Database definitions take precedence over YAML when both exist for the active profile.
Twig helper: nowo_cookie_consent_cookie_inventory().
The bundle ships CookieDefinitionAdminController and Bootstrap admin templates under @NowoCookieConsentBundle/admin/cookie_definition/. Import the controller route in your app (same pattern as your config admin). Forms use CookieDefinitionType with embedded translations.
Route name prefix: nowo_cookie_consent_cookie_definitions_*.
When enabled, optional categories show a cookie inventory table with an Allow column. Visitors can toggle individual cookies; category switches sync with per-cookie choices.
Enable globally (YAML default) or per profile when use_database_config: true:
nowo_cookie_consent:
use_cookie_inventory: true
granular_cookie_selection: true # bundle default; overridden by CookieConsentConfig when DB config is activeRequired cookies are always on and never appear in the granular toggle list.
Twig helper: nowo_cookie_consent_granular_cookie_selection().
Per-cookie consent is stored in the consent cookie JSON map. Check programmatically:
$cookieChecker->isCookieAllowedByUser('_ga', 'analytics');When disable_page_interaction is true, the modal adds a full-page overlay and blocks scrolling until the visitor accepts, rejects, or saves preferences.
Enable globally in YAML (works without Doctrine):
nowo_cookie_consent:
disable_page_interaction: trueWith use_database_config: true, the active CookieConsentConfig profile overrides the YAML default via disablePageInteraction.
Overlay opacity follows the active color_theme (--nowo-cc-overlay CSS variable). Twig helper: nowo_cookie_consent_disable_page_interaction().
When preferences_bubble_enabled is true, the bundle renders a fixed circular button with a cookie icon. It uses the same data-nowo-open-consent handler as manual “Cookie settings” links and opens the preferences step of the modal.
Keep the modal in the DOM after consent is saved:
{% if nowo_cookie_consent_should_embed_modal() %}
{{ render(path('nowo_cookie_consent.show_if_not_set')) }}
{% endif %}Position with preferences_bubble_position: bottom-right (default), bottom-left, top-right, or top-left.
Set preferences_bubble_border_color (hex, e.g. #60fed2) for the transparent bubble outline and cookie icon. With use_database_config: true, configure per profile via CookieConsentConfig::preferencesBubbleBorderColor in the admin settings form.
Set preferences_bubble_icon to custom SVG or HTML markup (e.g. an emoji wrapped in a <span>). With use_database_config: true, configure per profile via CookieConsentConfig::preferencesBubbleIcon in the admin settings form. Leave empty to use the default cookie SVG.
Twig helpers:
nowo_cookie_consent_preferences_bubble_enabled()nowo_cookie_consent_preferences_bubble_position()nowo_cookie_consent_should_embed_modal()
Override markup: templates/bundles/NowoCookieConsentBundle/cookie_consent_preferences_bubble.html.twig.
When two_step_modal is enabled on the profile, the preferences step includes a close control (data-nowo-hide-preferences) that returns to the compact consent banner without closing the modal.
When fetch_config_via_api is true, the bundle exposes JSON endpoints for client-side modal configuration:
| Route | Path |
|---|---|
nowo_cookie_consent.config |
/cookie-consent/config?locale=en |
nowo_cookie_consent.config_localized |
/{_locale}/cookie-consent/config |
Response shape:
{
"code": 200,
"data": {
"autoShow": true,
"guiOptions": { "consentModal": { "layout": "box", "position": "bottom center" } },
"language": {
"default": "en",
"translations": {
"en": {
"consentModal": {
"title": "Cookie settings",
"description": "We use cookies..."
}
}
}
}
}
}The frontend script reads data-nowo-config-url on the modal, performs a GET, and applies the payload before opening the modal.
Admin pages extend @NowoCookieConsentBundle/admin/base.html.twig, which extends web_ui.layout_template (Twig global nowo_cookie_consent_layout_template) and stacks stylesheets / javascripts with {{ parent() }} (REQ-UI-001). Prefer pointing layout_template at your project layout (or a thin bridge that maps nowo_ui_content into your body block) instead of copying list/form templates. The default admin/layout.html.twig is a full HTML document (no parent()).
ui_themevsweb_ui.css_frameworkThese are independent settings:
Setting Scope Default Controls ui_themePublic modal bootstrapMarkup variant for the GDPR consent modal rendered on the front-end ( bootstraportailwind). Does not affect admin pages.web_ui.css_frameworkAdmin UI bootstrap5CSS stack loaded in the bundle's admin layout. When set to bootstrap,bootstrap4,bootstrap5, ortablerthe Bootstrap 5 CDN link is included automatically. For any other value (custom,tailwind,none,foundation) the layout injects UiKitnowo-ui.cssvia packagenowo_ui_kit.Change
web_ui.css_frameworkwithout affecting the public modal, and vice-versa.
| Option | Default | Description |
|---|---|---|
web_ui.enabled |
true |
Registers admin access enforcement when security is configured |
web_ui.path_prefix |
/cookie-consent-config |
Documented URL prefix for host access_control |
web_ui.layout_template |
@NowoCookieConsentBundle/admin/layout.html.twig |
Root Twig layout extended by admin/base.html.twig |
web_ui.css_framework |
bootstrap5 |
Host CSS stack hint (bootstrap / bootstrap4 / bootstrap5 / tabler → Bootstrap CDN; custom / tailwind / none / foundation → UiKit nowo-ui.css via nowo_ui_kit) |
web_ui.icon_set |
bootstrap-icons |
Icon hint |
web_ui.list_page_size |
20 |
Page size for cookie definition admin lists |
nowo_cookie_consent:
web_ui:
layout_template: 'base.html.twig'
css_framework: bootstrap5 # change to custom/tailwind/none to drop Bootstrap CDN
list_page_size: 20Set web_ui.css_framework: custom (or tailwind / none) to remove the Bootstrap CDN dependency from the admin layout. The bundle then automatically links UiKit admin stylesheet (nowo-ui.css) from the nowo_ui_kit asset package:
nowo_cookie_consent:
web_ui:
css_framework: customUiKit nowo-ui.css ships semantic styles plus --nowo-ui-* CSS custom properties for easy token overrides. Override tokens in your host CSS:
/* Override design tokens scoped to the admin root */
.nowo-ui-root {
--nowo-ui-primary: #6366f1;
--nowo-ui-primary-hover: #4f46e5;
--nowo-ui-bg: #f9fafb;
--nowo-ui-border-radius: 0.5rem;
}If you supply your own host layout via web_ui.layout_template, load your CSS there and set css_framework: custom so the bundle does not inject its fallback stylesheet.
When using the project layout, load host CSS/JS in that layout; admin/base.html.twig stacks them with {{ parent() }} on both stylesheets and javascripts. Semantic hooks use nowo-ui-* classes. Legacy templates under admin/cookie_definition/layout.html.twig and admin/config/layout.html.twig remain as BC aliases that extend admin/base.html.twig.
nowo_cookie_consent:
security:
access_roles: [ROLE_ADMIN]
# access_checker: App\Security\CookieConsentAccessChecker
allow_unauthenticated: falseAlso lock the path in the host firewall:
# config/packages/security.yaml
security:
access_control:
- { path: ^/cookie-consent-config, roles: ROLE_ADMIN }allow_unauthenticated: true is for demos/CI only. Production must keep it false and require symfony/security-bundle. The default CookieConsentAccessCheckerInterface implementation grants access when any configured role is granted; set a custom security.access_checker service id to replace it.
REQ-TWIG-001. Namespace: NowoCookieConsentBundle. Full procedure, freeze rule, and the overridable <subpath> table: USAGE.md — Overriding templates. Theme-specific modal/form paths: UI theme. Prefer web_ui.layout_template over copying admin pages — Admin Web UI.
nowo_cookie_consent_is_saved()nowo_cookie_consent_is_category_allowed('analytics')nowo_cookie_consent_is_open_by_default(route, disabled_routes)nowo_cookie_consent_enabled_locales()nowo_cookie_consent_locale()nowo_cookie_consent_cookie_inventory()nowo_cookie_consent_granular_cookie_selection()nowo_cookie_consent_disable_page_interaction()nowo_cookie_consent_should_embed_modal()nowo_cookie_consent_should_render()nowo_cookie_consent_preferences_bubble_enabled()nowo_cookie_consent_preferences_bubble_position()nowo_cookie_consent_preferences_bubble_border_color()nowo_cookie_consent_preferences_bubble_icon()nowo_cookie_consent_two_step_modal()
When the application database schema is not ready (for example during Site Backup Bundle cold start), set one of these main-request attributes to false:
| Attribute | Constant |
|---|---|
_nowo_site_backup_schema_exists |
ColdStartRequestAttributes::SITE_BACKUP_SCHEMA_EXISTS (shared with Site Backup Bundle) |
_nowo_cookie_consent_schema_ready |
ColdStartRequestAttributes::COOKIE_CONSENT_SCHEMA_READY (local override) |
Effects on the main request:
CookieConsentConfigTranslationSubscriberskips config resolution.CookieConsentFormSubscriberskips consent form handling.nowo_cookie_consent_should_render()returnsfalse.
If Doctrine is still unreachable, subscribers log at debug and soft-skip on Doctrine\DBAL\Exception / Doctrine\ORM\Exception\ORMException only.
No bundle configuration keys are required when Site Backup already publishes _nowo_site_backup_schema_exists.