WebProCMS ships with a built-in cookie consent system that handles GDPR (EU/UK/Switzerland), CCPA/CPRA (California), and similar privacy laws around the world. It's off by default and adds zero overhead to sites that don't use it. When enabled, it shows a configurable consent banner, gates third-party tracking scripts until consent is granted, and replaces third-party iframe embeds (YouTube, Vimeo) with click-to-load placeholders.
When to turn it on
The legal triggers are based on the visitor's location, not where the business is located:
- GDPR / UK GDPR / Swiss FADP — Required for any visitor from the EEA, UK, or Switzerland if the site uses non-essential cookies (Google Analytics, Meta Pixel, ad networks, YouTube embeds, etc.). There is no revenue threshold — even a single EU visitor counts.
- CCPA / CPRA — Required for businesses serving California residents that cross at least one threshold: $25M+ annual revenue, or buying/selling/sharing personal info of 100,000+ CA consumers per year, or deriving 50%+ of revenue from selling/sharing CA consumer data. Most small businesses fall under the thresholds — but using Google Analytics + ad pixels often pushes them over the "sharing" definition.
- Most US states / rest of world — No federal law in the US yet; many other countries have GDPR-style rules.
Rule of thumb: if the site uses Google Analytics, Meta Pixel, ad pixels, YouTube embeds, or any third-party tracking — turn it on. The cost of an unnecessary banner is near zero; the cost of being audited without one is a percentage of revenue.
How it works
Five pieces, all built into the CMS:
- Consent banner — A built-in Alpine component themed by branding tokens. Renders on every public page when the feature is enabled. Offers Accept All / Reject All / Manage Preferences with per-category toggles (Necessary / Analytics / Marketing).
- Script gating — Every snippet on the Dashboard → Snippets page has a Consent Category dropdown. Snippets in the Analytics or Marketing categories render as inert
<template>blocks in the HTML; a small JS shim moves them into the live DOM (and executes any<script>tags) once the visitor grants consent for that category. - Embed gating —
<x-dl.video>(YouTube + Vimeo) renders a thumbnail + "Accept & load" placeholder when the marketing category isn't consented. The iframe URL lives indata-srcso the iframe never loads — and never sets cookies — until the visitor explicitly opts in. YouTube embeds also useyoutube-nocookie.comas the default domain even when consented, which is YouTube's privacy-enhanced subdomain. - Built-in GA4 relay (Settings → Analytics → Integrations) — when a GA4 Measurement ID is configured, the gtag.js bootstrap auto-injects into
<head>and is wrapped in the same consent-template shim, so GA's cookies don't load until the visitor grants the Analytics category. See addons/analytics.md. - Server-side consent check (
CookieConsent::hasGranted) — non-snippet integrations (currently: the analytics persistent visitor cookie) readwpcms_consentserver-side to decide whether to act. The helper reads the cookie, falls back to the mode's default-granted set when no cookie is present, and returnstruewhen the feature is OFF so disabled installs behave as if everything is granted.
Modes
| Mode | Behavior | When to use |
|---|---|---|
| Auto-detect by visitor country (recommended) | EEA/UK/CH visitors get opt-in (GDPR). US visitors get opt-out (CCPA/CPRA). Other countries fall back to the configured default. Uses Cloudflare's CF-IPCountry or CloudFront's CloudFront-Viewer-Country header. |
Default for any production site. Requires the site to be served through Cloudflare or CloudFront — most sites are. |
| Opt-in for everyone | Scripts blocked until consent. Strictest mode. | Sites with significant EU traffic, or operators who'd rather over-comply. |
| Opt-out for everyone | Scripts run by default; banner explains how to reject. | US-only sites with no expected EU traffic. |
| Off | System loaded, snippet/embed gating still respected, but no banner is shown and nothing fires automatically. | Staging environments, or testing that the gating works before going live. |
For Auto mode, when the visitor's country can't be resolved (no CDN header) or falls outside the EU/US buckets, a configurable Default for unknown regions falls back to one of off, opt_out, or opt_in.
Architecture: how the cache stays intact
WebProCMS uses Spatie's response cache for the public site — every page is cached once and served to every visitor. The consent system is designed to never multiply or invalidate that cache by visitor.
The cached HTML always contains the inert form of every gated thing:
- Gated snippets render as
<template data-consent-category="X" data-consent-placement="Y">…</template>— the browser ignores these completely. - Gated YouTube/Vimeo embeds render as
<iframe data-src="…">plus a visible placeholder — the iframe never loads. - The banner shell is in every cached page as
display:none.
All per-visitor variance happens client-side:
- Cookie check — the JS shim reads
wpcms_consentcookie on page load. Zero server round trip. - First-visit mode resolution — on the first session visit (no cookie yet), the shim hits the uncached
/_cookie-consent/initendpoint, which returns{mode, country, …}based on the visitor's CDN header. The browser caches this response for 30 minutes (Cache-Control: private, max-age=1800), so subsequent page loads are zero extra requests.
Net result: one cached HTML serves every visitor. Activation, banner visibility, and mode detection all happen in ~1KB of JS in the visitor's browser.
Snippet categories
Every entry on the Snippets page has a Consent Category:
- Necessary — Always runs, never gated. Use for: session/CSRF cookies, site-verification meta tags, structured-data JSON-LD, dark-mode preference persistence.
- Analytics — Runs only after the visitor grants analytics consent. Use for: Google Analytics, Plausible, Mixpanel, session replay tools.
- Marketing — Runs only after the visitor grants marketing consent. Use for: Google Tag Manager (when used for ads), Meta Pixel, LinkedIn Insight Tag, Google Ads conversion tracking, TikTok Pixel.
When the cookie consent feature is off, the category is ignored — every active snippet runs, exactly like today. Existing snippets default to Necessary on migration, so enabling the feature without re-categorizing won't break anything — but it also won't gate anything until you set the right categories.
Third-party iframe embeds
<x-dl.video> (the YouTube/Vimeo embed component) is wired into the consent system. When the feature is enabled:
- If the visitor hasn't granted marketing consent yet, the iframe URL is stored in
data-srcand a thumbnail+overlay placeholder is rendered instead. No iframe = no third-party cookies dropped. - The placeholder shows a configurable message + two buttons: Accept & load (grants marketing consent and activates this embed plus any other gated content site-wide) and Open on original site instead (opens the YouTube/Vimeo URL in a new tab — never loads the embed).
- When consent is granted, the JS shim swaps
data-src→srcon the iframe and hides the placeholder. The iframe loads usingyoutube-nocookie.com(YouTube) or the standard Vimeo player.
This same pattern extends to any future <x-dl.*> component that embeds third-party content: just render the data-consent-category="marketing" wrapper with data-src on the iframe and a placeholder div.
Configuration
All settings live under Dashboard → Settings → Privacy & Compliance → Cookie Consent:
- Enable/disable toggle
- Mode (Auto / Opt-in / Opt-out / Off)
- Default for unknown regions (Auto mode only)
- Banner position (Bottom bar / Bottom-left card / Bottom-right card / Center modal)
- Banner copy (title, body, accept/reject/manage/save labels)
- Embed placeholder copy (message, accept-and-load label, external-link label)
- Privacy policy URL (linked from the banner)
- Cookie lifetime in days (default 180)
Saving these settings clears the public response cache so the next visitor sees the new banner/markup immediately.
What it's not
- Not a privacy policy generator. Admins are responsible for their own privacy policy page (a starter template ships in the design library under
privacy-policy). - Not legal advice. The categories, copy, and mode mappings reflect common interpretations of the law — operators should consult counsel if their jurisdiction or use case is unusual.
- Not a GeoIP service. Country detection relies on CDN-provided headers (Cloudflare's
CF-IPCountry, CloudFront'sCloudFront-Viewer-Country, or Vercel'sX-Vercel-IP-Country). Installs not behind one of these CDNs will hit the Default for unknown regions fallback for every visitor. Paid IP-geolocation packages can be wired in via theApp\Support\VisitorRegionhelper. - Not a consent management platform. Categories are fixed at three (Necessary / Analytics / Marketing). There's no per-vendor disclosure UI like Cookiebot's "this site uses N vendors" view. The system is built for the common case of a small-to-medium business site, not for ad-network publishers running 200+ vendor scripts.