google tag manager8 min readBy Phloz team

Google Consent Mode v2 setup for agencies (the version that actually works)

Most Consent Mode v2 implementations are wrong in subtle ways that block 60-80% of EU conversions silently. This is the actual setup we ship for agency clients — what each consent signal does, how to wire it through GTM, and how to verify it didn't break overnight.

TL;DR

Consent Mode v2 has six consent signals (ad_storage, analytics_storage, ad_user_data, ad_personalization, functionality_storage, security_storage). Most implementations set defaults but never wire the cookie banner's "update consent" callback through to GTM, so every EU user shows as denied forever. The setup that works: (1) default consent set to denied for ad/analytics in EU regions only, (2) update consent triggered by the cookie banner's accept/decline callback, (3) advanced consent mode enabled (sends modeled conversions when consent is denied so you don't lose all signal), (4) per-tag consent requirements verified in GTM Preview. Verification takes 15 minutes per client. Skip any step and you'll quietly under-report 60-80% of EU conversions.


The problem with Consent Mode v2 isn't that it's hard. It's that it has six independent consent signals, two consent states (default vs. update), regional behavior, and a fork between "basic" and "advanced" mode — and most cookie-banner integrations get one of those wrong in a way that doesn't surface until the EU client's quarterly review shows ad spend that produced no measurable conversions.

This is the version we ship for agency clients. Not the official Google walkthrough; the one that survives the audits.

Consent Mode is Google's mechanism for letting Google tags (gtag.js, GA4, Google Ads) respect user consent without blocking the tags entirely. When consent is granted, tags fire normally with cookies. When consent is denied, tags fire in a degraded mode that:

  • Does NOT set or read cookies
  • Does NOT send personally-identifiable data
  • DOES send "consent-less pings" with anonymized session info that Google uses to model unobservable conversions

The "advanced consent mode" path (the one you want) uses these consent-less pings to attribute conversions that would otherwise be invisible. The "basic consent mode" path just blocks the tags entirely. The difference: basic loses 60-80% of EU conversion signal; advanced recovers most of it through modeling.

V2 (rolled out March 2024) added two new signals (ad_user_data, ad_personalization) that explicitly differentiate "is the data sent to Google" from "can Google use it for personalization." Before V2 these were conflated; the new granularity matters for EU regulatory compliance and unlocks the modeled conversions.

Get these wrong and you'll either over-block (lose conversions) or under-block (regulator visit).

SignalControlsDefault in EU
ad_storageCookies for ads (DoubleClick, conversion linker)denied
analytics_storageCookies for analytics (GA4, conversion measurement)denied
ad_user_dataWhether Google receives user identifiers for adsdenied
ad_personalizationWhether Google can use data for personalized adsdenied
functionality_storageSite functionality (preferences, language)granted (usually)
security_storageSecurity features (fraud detection)granted (always)

The four denied defaults are what differentiate EU compliance from US. In US (and most non-EU regions), all six default to granted and the cookie banner is informational rather than gatekeeping.

The setup that works

Five steps. Each one is independently a place to break things.

Not every cookie banner does. As of mid-2026, working integrations: OneTrust, Cookiebot, Iubenda, Usercentrics, CookieYes, Termly. NOT working / partial: most homegrown banner libraries, some older WordPress plugins.

The banner needs to:

  1. Set the default consent state on page load (before any other Google tags fire)
  2. Update the consent state when the user accepts/declines
  3. Persist the user's choice (cookie + localStorage)
  4. Re-apply on subsequent page loads

If the banner only does (1), every user looks denied forever. If it skips (2), accepting consent doesn't actually grant anything. Both are common.

In your <head>, before the GTM container snippet:

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

// Default to denied for EU; granted elsewhere. Region detection
// runs on the server (or via a CDN edge function) — never trust
// client-side region detection for compliance purposes.
gtag('consent', 'default', {
  'ad_storage': 'denied',
  'analytics_storage': 'denied',
  'ad_user_data': 'denied',
  'ad_personalization': 'denied',
  'functionality_storage': 'granted',
  'security_storage': 'granted',
  'wait_for_update': 500  // ms to wait for the banner to call gtag('consent', 'update', ...)
});
</script>
<!-- Now the GTM container snippet -->

The wait_for_update: 500 tells gtag to hold tag firing for 500ms while the cookie banner has a chance to call gtag('consent', 'update', ...). Without this, tags fire under the default denied state even if the user has consented in a previous session.

Most cookie-banner platforms have a built-in Google Consent Mode integration that handles this. Verify the integration is enabled:

  • OneTrust: Cookie Categories → Google Consent Mode → enable per category
  • Cookiebot: Settings → Google Consent Mode → enable, set per category
  • Iubenda: Configuration → Tag Management → enable Google Consent Mode

If your banner doesn't have a built-in integration, you write the JS yourself in the banner's accept/decline callback:

// In the banner's `onAccept` callback:
gtag('consent', 'update', {
  'ad_storage': 'granted',
  'analytics_storage': 'granted',
  'ad_user_data': 'granted',
  'ad_personalization': 'granted'
});

// In the banner's `onDecline` callback:
// (Don't need to call update — defaults are already 'denied'.)

Google's advanced consent mode sends consent-less pings that allow modeling of denied-state conversions. Enable in:

  • Google Ads: Admin → Linked Accounts → Cookies → enable Advanced Consent Mode
  • GA4: Admin → Data collection and modification → Data settings → Data collection → enable "Collect ads-personalization data even from regions requiring consent" (the toggle name varies by GA4 version)

Without advanced consent mode, denied users contribute zero data. With it, denied users contribute consent-less pings that get modeled into your reports — typically recovering 50-70% of the would-be-lost conversions.

Each tag in GTM has a consent requirement. Verify it matches the consent signal it actually depends on:

  • GA4 Configuration tag → requires analytics_storage
  • Google Ads Conversion tag → requires ad_storage
  • Google Ads Remarketing tag → requires ad_storage + ad_user_data + ad_personalization
  • Custom HTML tags → may not auto-detect; set manually

In GTM: Tag Configuration → Consent Settings → "Require additional consent for tag to fire" → check the right boxes.

The most common mistake here: leaving Custom HTML tags with no consent requirement, so they fire even when consent is denied. Regulator-visible.

How to verify it actually works

Three checks, 15 minutes total per client:

Check 1: GTM Preview mode

Open Preview on the live container. Refresh the page in Preview tab. Look at the "Consent" tab:

  • Default state should match what your <head> script sets (denied for ad/analytics in EU)
  • After accepting consent in the banner, "Update" event should appear with all four ad/analytics signals → granted
  • Tags listed under "Tags Fired" should match expected consent-respecting behavior

If "Update" event doesn't appear when you accept consent: your banner isn't calling gtag('consent', 'update', ...). Step 3 is broken.

Check 2: Tag Assistant Companion (Chrome extension)

Install Tag Assistant. Visit the live site. Tag Assistant shows the consent state in real time + which tags fired with which consent. Easier than GTM Preview for non-developers on the agency team.

Check 3: Google Ads / GA4 dashboard delta

Compare the "EU traffic" segment in GA4 before and after your Consent Mode rollout. A working setup shows:

  • Total events drop by ~30-40% in EU (from blocked tags)
  • Modeled conversions appear in GA4 reports (look for "(modeled)" annotation)
  • Ad Manager Conversion volume in EU recovers most of its pre-rollout baseline within 2-3 weeks

If you see a 60-80% drop in EU events with NO modeled-conversion recovery: advanced consent mode isn't enabled (Step 4) or banner integration isn't wired (Step 3).

The agency-side discipline

Consent Mode is a per-client setup, but the discipline is per-agency. Three things to standardize:

  1. A house standard cookie banner. Pick one (OneTrust, Cookiebot, Iubenda, etc.) and use it on every EU-touching client. Per-client banner choice fragments your engineer's debugging skill.
  2. A house template for the <head> consent block. The 5-step setup above as a snippet you paste into every client. Variations are bug surfaces.
  3. A monthly EU-traffic delta check. Add it to the 21-item checklist. If EU conversions dropped >70% month-over-month, something's wrong.

What's NOT in this post

We're deliberately not covering:

  • The legal/regulatory side of consent (CMP requirements, IAB TCF, country-specific rules) — that's a lawyer question
  • Server-side consent enforcement (advanced topic; most agencies don't need it for V1 setup)
  • Consent-aware audience segmentation (V2 of consent maturity; most agencies should get the basics right first)

The setup above is the minimum that works. Get this right, then layer on the advanced topics as the client engagement matures.

The takeaway

Consent Mode v2 is five steps that most agencies do four of. The fifth step — wiring gtag('consent', 'update', ...) from the banner — is the one that breaks most often, and it's silent when broken. Verify with GTM Preview after every banner change, every banner version upgrade, and every quarterly review. Skip the verification and you'll quietly lose 60-80% of EU conversion data without anyone noticing until the renewal conversation.

Try Phloz free if you want consent-mode state tracked as a typed node per client with a "last verified" timestamp baked in.