CMS Plugin · Privacy

GDPR / DSGVO Cookie Consent

A GDPR/DSGVO-compliant cookie-consent layer shipped as a CMS widget you drop into a layout — equal-prominence Accept / Reject / Customize, granular categories, and Google Consent Mode v2.

What it is

A GDPR / DSGVO-compliant cookie-consent layer for the public storefront — shipped not as a separate plugin but as a CMS vue-component widget an admin drops into a layout through the existing layout editor. No new plugin, no core change: it lives entirely in the three cms plugin repos (fe-user, fe-admin, backend).

Consent gates cookies, not people. Strictly-necessary flows — login, cart, checkout — are never blocked (they are exempt from prior consent). What consent controls is which scripts and trackers may run, via Google Consent Mode v2.

On first visit (or after the consent policy version is bumped) the widget renders a dialog with three equally-prominent choices:

First layer — Accept all, Reject all and Customize at equal prominence, plus a privacy-policy link (informed consent).
First layer — Accept all, Reject all and Customize at equal prominence, plus a privacy-policy link (informed consent).

The consent dialog — two layers

Layer 1 states the purpose, links the privacy policy, and offers three buttons of equal prominenceAccept all, Reject all, Customize. Equal styling is a DSGVO requirement: refusing must be as easy as accepting, with no dark-pattern nudging.

Layer 2 (Customize) gives one toggle per category with its purpose copy, and a Save my choices button. Strictly necessary is locked on and cannot be disabled; Preferences, Statistics and Marketing default to off (no pre-ticked boxes).

Second layer — granular per-category toggles (necessary locked on) and Save my choices.
Second layer — granular per-category toggles (necessary locked on) and Save my choices.

How consent is enforced — Consent Mode v2

The widget does not bundle an analytics SDK; it only signals consent. It emits Google Consent Mode v2 signals so any consent-aware tag (a CMS CustomCode snippet, a gtag tag, …) respects the decision automatically:

  • Default = denied for every non-necessary signal, pushed to dataLayer as early as possible (queued even if gtag has not loaded yet) — so nothing tracks before a decision.
  • On decisiongtag('consent','update', …) mapping categories to signals: statistics→analytics_storage, marketing→ad_storage / ad_user_data / ad_personalization, preferences→functionality_storage / personalization_storage.
  • Non-gtag consumers read the same truth via useConsent().granted(category) or the vbwd:consent-changed window event.

The decision is stored client-side, versioned and withdrawable:

// localStorage key: "vbwd_cookie_consent"
{
  "version": 1,                 // = config.consent_version; stale => re-prompt
  "decidedAt": "2026-06-27T12:00:00Z",
  "method": "accept_all" | "reject_all" | "custom",
  "categories": { "necessary": true, "preferences": false,
                  "statistics": false, "marketing": false }
}

Adding it to your site

Because it is a CMS widget, there is nothing to install — it is a seeded record an admin places in a layout:

  1. CMS → Widgets — the “Cookie Consent (GDPR/DSGVO)” record is in the picker. Open it to set the privacy-policy URL, the position, the dim level, the consent version, which optional categories appear, and whether the persistent settings button shows.
  2. CMS → Layouts — edit the public layout and drag the Cookie Consent widget into any area (the footer is the natural home — the overlay self-positions). Save.
  3. The component teleports to <body>, so it shows site-wide on every page that uses that layout, on first visit.
The fe-admin widget editor — privacy URL, position, site-dim blend, consent version, optional categories and the persistent settings button.
The fe-admin widget editor — privacy URL, position, site-dim blend, consent version, optional categories and the persistent settings button.

Modal or bottom bar

The Position setting chooses how the dialog appears — a centred modal or a bottom bar — while the dim overlay still covers the page either way. An optional Additional text line and a Blend the site slider (how much the page behind is dimmed) let each site tune the look without any custom CSS.

The bottom-bar variant on the storefront home page, with the configurable banner text.
The bottom-bar variant on the storefront home page, with the configurable banner text.

Withdrawing & changing consent

Consent must be as easy to withdraw as to give. After a decision the dialog hides and a persistent “Cookie settings” affordance remains so a visitor can reopen the dialog and change their mind — switching to Reject re-emits denied signals immediately. A CMS footer-menu link can also reopen it by dispatching the vbwd:open-cookie-consent window event (decoupled — no menu coupling).

Bumping Consent Version in the widget config marks every stored decision stale, so all visitors are re-prompted after a policy change.

Compliance map

RequirementHow it is met
Prior consent for non-essential trackersConsent Mode v2 default = denied; tags fire only after an explicit update.
Strictly-necessary exemptnecessary is always on; login / cart / checkout are never gated.
As easy to refuse as to acceptReject all sits on layer 1 with equal prominence to Accept all.
Granular & specificLayer-2 per-category toggles with purpose copy and Save my choices.
InformedPurpose text per category + a configurable privacy-policy link.
Freely given / withdrawablePersistent Cookie settings affordance + reopen event; reject re-denies instantly.
Proof / recordVersioned localStorage record (version, decidedAt, method, categories); a server-side audit record is the documented upgrade.
Re-consent on policy changeBump consent_version ⇒ everyone is re-prompted.
No dark patternsEqual button styling, no pre-ticked non-necessary toggles, refuse never buried.

Under the hood

It rides the CMS three-layer widget pipeline — no new model, migration, renderer or SDK change:

  • fe-user (vbwd-fe-user/plugins/cms/) — CookieConsent.vue (teleports a focus-trapped overlay to body, reusing the fe-core design system), the useConsent() composable (one source of truth) and the consentMode.ts bridge; registered via registerCmsVueComponent('CookieConsent', …).
  • fe-admin (vbwd-fe-admin/plugins/cms-admin/) — a widget-editor descriptor (registerWidgetEditor) supplying the config form and a live preview.
  • backend (vbwd-backend/plugins/cms/) — populate_cms.py seeds the cookie-consent CmsWidget record (content_json.component == 'CookieConsent') so it appears in the picker; settings ride the existing CmsWidget.config JSON.

See the CMS plugin reference for the widget system this builds on.