Skip to main content

Consent Mode v2: A Practical Compliance Playbook

How to combine Consent Mode v2 signals with your CMP and server-side routing to maximize data recovery while staying GDPR-compliant.

Most Consent Mode v2 implementations we look at are technically present and functionally broken. The CMP is installed, the banner shows, the account manager ticked the box in the Google Ads interface, and yet the consent signal reaching Google is either wrong or arriving too late to matter. The failure is quiet: nothing errors, nothing turns red, and the first sign of trouble is a remarketing audience that stops growing.

This is a working playbook rather than a compliance lecture. What v2 added, what order things must happen in, what actually occurs to your data on a denial, and how to prove any of it to someone who asks.

What Consent Mode v2 actually added

Version 1 gave you two signals that mattered: ad_storage and analytics_storage. Both were about cookies. Could a Google tag write and read storage on this device, yes or no.

Version 2 added two signals that are not about storage at all. ad_user_data asks whether you may send personal data to Google for advertising purposes. ad_personalization asks whether that data may be used for personalized advertising, meaning remarketing and similar audiences. They exist because the Digital Markets Act requires a separate legal basis for sending data and for using it to personalize, and a cookie permission doesn't cover either.

The practical consequence: a site can have ad_storage granted and still be barred from remarketing, because the user declined personalization. If your CMP maps all four signals to a single "Accept marketing" toggle, you're not wrong exactly, but you've thrown away a distinction some regulators care about and some CMPs surface as separate purposes.

SignalGovernsBehavior when denied
ad_storageAdvertising cookies and device identifiersNo _gcl_* cookies written or read. Click IDs redacted if ads_data_redaction is on
ad_user_dataSending user data to Google for adsEnhanced conversions and user-provided data are suppressed
ad_personalizationRemarketing and personalized adsUser excluded from remarketing lists and similar audiences
analytics_storageAnalytics cookies, chiefly _gaNo client ID persisted. Cookieless pings still sent for modeling
functionality_storageSite preferences, language, layoutRarely used by Google tags. Often granted by default
security_storageFraud prevention, authenticationAlmost always granted. Treated as strictly necessary

Basic mode versus advanced mode

You get one architectural choice, and it determines everything downstream. In basic consent mode, Google tags are blocked from loading until the user grants consent. Nothing fires, no request leaves the browser, no ping of any kind. In advanced mode, the tags load immediately and send a cookieless ping carrying the current consent state, then upgrade to full measurement if consent arrives.

Advanced mode is what makes conversion modeling and behavioral modeling possible, because Google needs those denied-state pings to have anything to model from. Basic mode gives you a cleaner story for a strict DPO and a much larger hole in your data.

Pick deliberately and write the decision down. The most common accidental state is a site that believes it runs advanced mode while the CMP blocks the GTM container script itself, which turns it into basic mode with extra steps and no modeling.

Detecting that takes thirty seconds. Load a page in incognito, decline everything, and look for gtm.js in the Network panel. If the container never loads, or if it loads only after you accept, you're in basic mode regardless of what the CMP settings screen claims. The usual culprit is a script-blocking rule in the CMP that matches googletagmanager.com by hostname, added years ago by someone being careful and never revisited.

Why implementation order breaks setups

This is the single most common failure, and it's an ordering problem, not a configuration one.

The default consent state must be set before any Google tag executes. If a GA4 tag fires and then your CMP announces the defaults a beat later, that first hit went out under whatever the implicit state was, which is granted. One page view of leaked data per session, on every session, is exactly the thing an audit finds.

In GTM this is what the Consent Initialization trigger exists for. It fires before Initialization, which fires before All Pages. Your CMP template tag, or a Custom HTML tag containing the gtag('consent', 'default', ...) call, goes on that trigger and nothing else does. Do not put a GA4 tag on Consent Initialization to "make sure it runs early." That inverts the whole point.

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){ dataLayer.push(arguments); }

  // Regional default: deny everything non-essential
  gtag('consent', 'default', {
    'ad_storage': 'denied',
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'analytics_storage': 'denied',
    'functionality_storage': 'granted',
    'security_storage': 'granted',
    'wait_for_update': 500,
    'region': ['AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE',
               'GR','HU','IE','IT','LV','LT','LU','MT','NL','PL','PT',
               'RO','SK','SI','ES','SE','GB','CH','NO','IS','LI']
  });

  // Global fallback for everywhere else
  gtag('consent', 'default', {
    'ad_storage': 'granted',
    'ad_user_data': 'granted',
    'ad_personalization': 'granted',
    'analytics_storage': 'granted',
    'functionality_storage': 'granted',
    'security_storage': 'granted'
  });

  gtag('set', 'ads_data_redaction', true);
  gtag('set', 'url_passthrough', true);
</script>

Two parameters in there earn their place. wait_for_update tells Google tags to hold for a stated number of milliseconds while an asynchronous CMP resolves the stored choice, which prevents a returning visitor who already consented from being counted as denied. Set it to 500 and lower it only if you've measured your CMP resolving faster. url_passthrough keeps gclid, gbraid, and wbraid moving through internal navigations in the URL when cookies aren't available, which is the difference between an attributable conversion and an unattributable one.

Region settings without over-engineering

The region key lets you set different defaults for different places, using ISO 3166-2 codes: two-letter country codes like FR, or country-subdivision codes like US-CA and CA-QC. Google evaluates the calls in order and the most specific matching region wins, with a region-free call acting as the fallback for everywhere else.

That ordering rule bites people. If you write a global granted default first and a European denied default second, the European one still applies, because specificity beats sequence. But if you write two overlapping regional calls, the later one wins for the overlap. Keep it to one regional block plus one global fallback and you'll never have to reason about it.

The interesting question is which regions to include. The EEA plus the UK and Switzerland is the baseline nearly everyone runs. California, Colorado, Connecticut, Virginia, and a growing list of US states have opt-out regimes rather than opt-in ones, which means denying by default there is legally unnecessary and costs you data. Brazil's LGPD and Quebec's Law 25 have their own shapes again.

Two failure modes to avoid. Setting a denied default worldwide because it feels safer throws away measurement in jurisdictions that never required it, and no regulator gives you credit for over-collecting consent. Setting it per country from a hand-maintained list means the list goes stale the moment a new law passes. Let the CMP own the geographic logic where it can, since that's what you pay it for, and keep your GTM-side defaults as the safety net for when the CMP fails to load at all.

Wiring the CMP update correctly

Once the user interacts with the banner, the CMP issues gtag('consent', 'update', {...}) with the granted values. Most commercial CMPs do this for you if you enable their Google Consent Mode integration, and most of them do it correctly. The part people miss is the GTM side.

Every tag in your container has a Consent Settings section under Advanced Settings. Google's own templates declare built-in consent checks, so a GA4 tag already knows to respect analytics_storage. Custom HTML tags and most third-party templates declare nothing. For those, set "Require additional consent for tag to fire" and name the signals. A Meta pixel in a Custom HTML tag with no consent settings fires on denial, and nothing in the interface warns you.

The Consent Overview screen (the shield icon at the top of the Tags list) shows every tag split into consent-configured and not-configured. Get that list to zero unconfigured, then check it again after every sprint where someone added tags.

What denied consent actually does

Denial does not mean silence in advanced mode, and this surprises people who expect zero network activity. Google tags still send a request, but it carries no cookies, no client ID, and no user identifiers. The consent state travels in the gcs parameter, which you can read straight off the request in DevTools.

Valuead_storageanalytics_storageWhat you should see
G100DeniedDeniedBefore interaction, or full rejection
G101DeniedGrantedAnalytics-only acceptance
G110GrantedDeniedUnusual. Check your CMP purpose mapping
G111GrantedGrantedFull acceptance

A companion parameter, gcd, encodes the fuller v2 state including ad_user_data and ad_personalization. It's less readable but it's the one that proves v2 signals are present rather than just v1 signals with new names.

Those cookieless pings feed two modeling systems. Conversion modeling in Google Ads estimates conversions from consented users that resemble the unconsented ones. Behavioral modeling in GA4 fills user and session metrics for the denied population. Both require your property to clear a daily traffic threshold that Google publishes and does not negotiate, and plenty of mid-size sites never reach it. If your GA4 reports show no "modeled" data indicator after weeks of correct implementation, volume is usually the reason, not a broken setup.

Testing by walking the site twice

Reading the container tells you what should happen. Walking the site tells you what does. Do both passes in an incognito window so stored consent doesn't contaminate the result.

Pass one, reject everything. Land on a page from a URL with a fake ?gclid=test123 appended. Before touching the banner, open Network and filter on collect. You should see a request with gcs=G100. Check Application, then Cookies: no _ga, no _gcl_aw. Now click Reject. Navigate to two more pages and repeat the cookie check. Then click through to a conversion and confirm no ad platform pixel appears in Network.

Pass two, accept everything. Fresh incognito window, same landing URL. Accept, then confirm _ga and _gcl_aw appear, that the next collect request carries gcs=G111, and that your conversion event reaches every destination it should.

Run both passes in GTM Preview as well. Each event in the Preview timeline has a Consent tab showing on-page default, on-page update, and the resulting state at the moment that tag evaluated. When a tag fired that shouldn't have, that tab tells you whether the update arrived late or the tag simply had no consent settings.

One extra check worth building into the routine: reload mid-session after accepting. A CMP that fails to restore stored consent on the second page load produces the classic pattern of high consent rates and terrible data quality.

The quarterly consent audit

Consent setups decay because containers change and CMPs update their vendor lists. A short recurring review catches the drift:

Put it on a calendar with a named owner. Consent work that belongs to "the team" belongs to nobody.

Documentation regulators actually ask for

If a supervisory authority or an enterprise procurement team asks, they don't want your GTM login. They want evidence that the described behavior matches the deployed behavior on a specific date. Keep a folder with:

  1. Screenshots of the banner in its first-layer and second-layer states, dated, for each region variant.
  2. A data flow list naming every destination that receives event data, what it receives, and which consent signal gates it.
  3. Exported GTM container versions at each quarterly review, which gives you a timestamped record of what was live.
  4. Consent receipts from the CMP, with retention long enough to cover your longest attribution window plus any statutory period.
  5. A one-page note stating whether you run basic or advanced consent mode and why.

The question is almost never "did you have a banner." It's "can you show what your site did on the fourteenth of March for a user who declined." A container export and a dated walkthrough answer that in five minutes.

Getting from broken to defensible

If you inherited a setup and don't know its state, do this in order. Confirm the defaults fire on Consent Initialization and nowhere else. Get the Consent Overview list to zero unconfigured tags. Add wait_for_update and url_passthrough. Then run the two walkthrough passes and fix whatever they surface.

That sequence takes an afternoon on most containers and moves you from "probably fine" to something you can actually demonstrate. The demonstration is the part that matters, because a correct implementation you can't evidence and an incorrect one look identical from the outside.