Skip to main content

Documentation

No results found.
Features

Cookie Consent

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 enab...

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:

  1. 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).
  2. 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.
  3. Embed gating — <x-dl.video> (YouTube + Vimeo) renders a thumbnail + "Accept & load" placeholder when the marketing category isn't consented. The iframe URL lives in data-src so the iframe never loads — and never sets cookies — until the visitor explicitly opts in. YouTube embeds also use youtube-nocookie.com as the default domain even when consented, which is YouTube's privacy-enhanced subdomain.
  4. 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.
  5. Server-side consent check (CookieConsent::hasGranted) — non-snippet integrations (currently: the analytics persistent visitor cookie) read wpcms_consent server-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 returns true when 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_consent cookie 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/init endpoint, 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-src and 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 → src on the iframe and hides the placeholder. The iframe loads using youtube-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's CloudFront-Viewer-Country, or Vercel's X-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 the App\Support\VisitorRegion helper.
  • 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.