ServicesAboutNotesContact Get in touch →
EN FR
Note

Consent Mode Implementation Mechanics

The technical implementation of Consent Mode v2: default state configuration, CMP integration, GTM trigger ordering, and the wait_for_update race condition.

Planted
ga4google adsanalyticsdata quality

A correct Consent Mode implementation requires three components in sequence: default consent states set before any tags fire, CMP communication of user choices through the Consent Mode API, and timing management to avoid race conditions. Most implementation failures trace back to one of these three components being misconfigured or misordered.

The default consent command must fire before any measurement tags load. This sets the baseline assumption for all four consent parameters until the user makes a choice.

In GTM, this means using the Consent Initialization - All Pages trigger. GTM has a specific trigger execution order:

  1. Consent Initialization - All Pages — fires first, before anything else
  2. Initialization - All Pages — fires second, typically where GA4 config tags load
  3. All Pages — fires third, where most event tags run

The default consent command belongs in step 1. If it fires in step 2 or 3 (a common mistake), measurement tags in earlier trigger groups load before consent state is established. Those tags assume consent is granted — the default when no consent command has been processed — and fire unconditionally.

gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500
});

For sites with global traffic, region-specific defaults prevent unnecessary data loss from non-EEA visitors while maintaining compliance for EEA/UK users:

// EEA and UK: deny by default, require opt-in
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'region': ['EEA', 'GB'],
'wait_for_update': 500
});
// US: grant by default (opt-out model)
gtag('consent', 'default', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted',
'analytics_storage': 'granted',
'region': ['US']
});

Region specificity follows a most-specific-wins rule. A US-CA region setting overrides a general US setting. This lets you apply stricter defaults for California (CCPA) while keeping the rest of the US on a more permissive opt-out model.

Setting consent to denied globally — including for US traffic — means you unnecessarily lose data from visitors in regions where opt-in isn’t required. Setting it to granted globally violates GDPR for EEA visitors. Region-specific defaults solve both problems.

The wait_for_update Parameter

This parameter addresses a race condition that silently breaks many implementations. Most CMPs load asynchronously. The CMP script needs to download, initialize, check for a stored consent preference, and then fire a consent update call. This takes time — typically 100 to 500 milliseconds.

Without wait_for_update, tags evaluate their consent state immediately after the default command sets everything to denied. If the CMP hasn’t fired its update yet (which is likely, given async loading), tags see denied and behave accordingly. In Basic mode, they don’t fire at all. In Advanced mode, they send cookieless pings. Even if the CMP fires its update 200ms later, the damage is done — the initial tag evaluation already happened.

wait_for_update: 500 tells tags to hold for up to 500 milliseconds after the default command before evaluating consent state. If the CMP fires an update within that window, tags use the updated state. If the CMP doesn’t respond within 500ms (slow network, CMP loading issues), tags fall back to the default denied state.

500ms is a safe default for most CMPs on reasonable connections. Some implementations use 300ms for faster pages or 1000ms for sites with heavy CMP configurations. The trade-off is page load performance: during the wait period, tags are blocked from firing even if consent state is actually known.

CMP Integration

The Consent Management Platform captures user choices (Accept, Reject, granular preferences) and must communicate them to Google tags via the Consent Mode API. When a user clicks Accept:

gtag('consent', 'update', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted',
'analytics_storage': 'granted'
});

The critical verification: all four v2 parameters must appear in the update call. Many older CMP configurations — set up before November 2023 when v2 launched — only send the two v1 parameters (ad_storage and analytics_storage). When ad_user_data and ad_personalization are missing from the update, Google assumes they remain denied. This is one of the most common failure modes: conversion tracking appears to work, but Enhanced Conversions data gets silently dropped and remarketing audiences stop building.

Certified CMPs

Google requires publishers using AdSense, Ad Manager, or AdMob to use a Google-certified CMP integrated with TCF v2.2 for personalized ads in EEA/UK. Major certified CMPs include Cookiebot, OneTrust, Didomi, CookieYes, and Axeptio.

Certified CMPs handle the gtag('consent', 'update', ...) call automatically when configured correctly. But “configured correctly” is the operative phrase. Common configuration gaps:

  • CMP installed but Consent Mode integration not enabled (it’s often a separate toggle)
  • CMP configured for v1 parameters only (an older template or version)
  • CMP stores consent in cookies but doesn’t fire the gtag update call (visual compliance without technical compliance)
  • CMP fires the update but with incorrect parameter mapping (e.g., mapping TCF purpose 1 to analytics_storage but not to ad_user_data)

Verification

The most reliable way to verify CMP integration isn’t through the CMP’s admin panel — it’s by inspecting what actually gets sent. Use the gcs and gcd network parameters to confirm all four parameters are being set correctly in both the default and update states. The Consent Mode Inspector Chrome extension provides real-time visibility without digging through network requests.

Non-GTM Implementations

If you’re implementing Consent Mode outside of GTM (directly in site code or through another tag manager), the same principles apply but the trigger ordering mechanism differs:

  1. The gtag('consent', 'default', ...) call must execute before the gtag('js', ...) call that initializes Google tags
  2. Your CMP callback must call gtag('consent', 'update', ...) when the user makes a choice
  3. The gtag library itself must be loaded (the consent command queues if gtag hasn’t loaded yet, which is fine)
<!-- 1. Set defaults FIRST -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500
});
</script>
<!-- 2. Load gtag.js -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
<script>
gtag('js', new Date());
gtag('config', 'G-XXXXXXXX');
</script>
<!-- 3. CMP fires update on user choice (handled by CMP script) -->

The ordering guarantee comes from script placement in the HTML rather than from GTM’s trigger system. The default command is synchronous and inline, so it always executes before the async gtag.js script loads.