What is Consent Mode v2?

Consent Mode is a Google API that lets your website communicate user consent choices to Google tags (Analytics, Ads, Floodlight). Instead of blocking tags entirely when consent is denied, Consent Mode adjusts tag behaviour: cookieless pings replace full tracking, preserving some measurement while respecting the user's choice.

The original Consent Mode (v1) had two signals: ad_storage and analytics_storage. In November 2023, Google introduced Consent Mode v2 with two additional signals required for EU compliance.

🚨
March 2024 deadline has passed

Google's enforcement began in March 2024. As of 2026, sites without Consent Mode v2 cannot use remarketing audiences, conversion modelling, or behavioural reporting for EEA users in Google Ads and GA4. This is not a future requirement. It is actively enforced.

The four consent signals

Consent Mode v2 requires you to declare four consent states before any Google tag fires. Two are from v1, two are new:

Signal What It Controls Version Default
ad_storage Whether advertising cookies (e.g., _gcl_au, _gcl_aw) can be stored on the device v1 (original) Set to denied
analytics_storage Whether analytics cookies (e.g., _ga, _gid) can be stored on the device v1 (original) Set to denied
ad_user_data Whether user data (email, phone) can be sent to Google for advertising (Enhanced Conversions, Customer Match) v2 (new) Set to denied
ad_personalization Whether personalised advertising (remarketing, audience targeting) is enabled v2 (new) Set to denied
💡
Why two new signals?

The EU's Digital Markets Act (DMA) requires Google to prove that personal data sent to its ad platform has explicit user consent. ad_user_data and ad_personalization map directly to DMA requirements. Without them, Google treats all EEA data as unconsented, which disables most advertising features.

Step-by-step implementation for GA4 + GTM

There are two paths: direct gtag.js implementation (if you load GA4 directly) and GTM-based implementation (if you use Google Tag Manager). Choose the one that matches your setup.

Path A: Direct gtag.js implementation

1
Set consent defaults before any Google tag loads

This must run in the <head>, before the gtag.js script tag. If it runs after, the first page view fires without consent context.

In <head> — before gtag.js
<!-- Consent defaults — MUST come before gtag.js -->
<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  // ms to wait for consent banner response
  });
</script>

<!-- Now 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>
2
Update consent when the user makes a choice

When the user clicks "Accept" (or selects specific categories), fire gtag('consent', 'update', ...) with the appropriate values. This call tells Google tags to switch from cookieless pings to full tracking.

After user accepts all cookies
// User clicked "Accept All"
gtag('consent', 'update', {
  ad_storage: 'granted',
  ad_user_data: 'granted',
  ad_personalization: 'granted',
  analytics_storage: 'granted'
});
After user accepts only analytics
// User clicked "Accept Analytics Only"
gtag('consent', 'update', {
  ad_storage: 'denied',
  ad_user_data: 'denied',
  ad_personalization: 'denied',
  analytics_storage: 'granted'
});
3
Persist consent and re-apply on return visits

Store the user's choice in localStorage or a first-party cookie. On subsequent page loads, read the stored value and call gtag('consent', 'update', ...) immediately after setting defaults. This ensures returning users don't see the banner again and their consent state is respected from the first page view.

Restoring consent on page load
// After gtag('consent', 'default', ...) runs:
const stored = localStorage.getItem('consent_state');
if (stored) {
  const consent = JSON.parse(stored);
  gtag('consent', 'update', consent);
}

Path B: GTM-based implementation

1
Enable Consent Overview in GTM

In your GTM container, go to Admin → Container Settings → Enable Consent Overview. This unlocks consent configuration in all tag settings.

2
Set default consent via a Consent Initialization trigger

Create a new tag of type "Google tag" (or use a Custom HTML tag). Set its trigger to Consent Initialization - All Pages (this fires before all other triggers). Push the same four consent defaults via the gtag('consent', 'default', ...) call.

3
Configure each tag's consent requirements

For every GA4 and Google Ads tag in GTM, open its settings and check the Consent Settings panel. Set which consent types are required. GA4 tags should require analytics_storage. Google Ads tags should require ad_storage, ad_user_data, and ad_personalization.

4
Push consent updates from your CMP

Your Consent Management Platform (or custom banner) should push consent updates to the dataLayer. GTM listens for these events automatically:

CMP pushes consent to dataLayer
window.dataLayer.push({
  event: 'consent_update',
  // GTM reads consent from gtag calls, not this event directly.
  // Your CMP should call gtag('consent', 'update', ...) as in Path A.
});
⚠️
GTM does not handle consent for you

GTM's consent features control when tags fire, but they don't create a consent banner or record user choices. You still need a CMP or custom banner that captures the user's explicit consent and fires gtag('consent', 'update', ...). Unlike OneTrust which requires enterprise setup, CookieGuard verifies your Consent Mode v2 signals are firing correctly in 30 seconds.

How to verify it's working

Implementation without verification is guesswork. Here are the three methods, ranked by reliability:

1. Chrome DevTools (quickest)

  1. Open your site in Chrome. Open DevTools (F12).
  2. Go to the Network tab. Filter by google or collect.
  3. Load the page without accepting cookies.
  4. Find the collect?v=2 request (GA4 measurement protocol hit).
  5. Check the query parameters: look for gcs= (Google Consent State). The value encodes your four consent signals:
    • gcs=G100 = all denied (correct default)
    • gcs=G111 = all granted
    • Any gcs=G1 prefix confirms Consent Mode is active
  6. Now accept cookies and check the next request. gcs= should change to reflect the granted state.

2. Google Tag Assistant (most detailed)

  1. Install the Tag Assistant Companion Chrome extension.
  2. Open Tag Assistant and connect to your site.
  3. Look for the Consent tab in the event timeline. It shows the consent state at each tag fire.
  4. Verify that the initial state shows all four signals as denied.
  5. Accept cookies and confirm the signals update to granted.

3. GA4 DebugView (most complete)

  1. Enable GA4 DebugView: add ?debug_mode=true to your URL or set debug_mode: true in your gtag config.
  2. Open GA4 → Admin → DebugView.
  3. Watch real-time events. Each event shows a consent state icon. A shield icon indicates cookieless mode (consent denied).

Automate Consent Mode v2 verification

CookieGuard scans your site and checks that consent defaults fire correctly. No manual DevTools needed.

Scan my site →

Common mistakes (and how to fix them)

1. Defaulting to granted

🚫
This is the #1 implementation mistake

Setting ad_storage: 'granted' as the default means you're tracking every user from the first page view, then "downgrading" if they reject. That's not consent. Under GDPR, consent must be opt-in, not opt-out. Default to 'denied' for all four signals. Always.

❌ Wrong — defaults to granted
gtag('consent', 'default', {
  ad_storage: 'granted',       // ❌ Violates GDPR — opt-out model
  ad_user_data: 'granted',    // ❌ Same problem
  ad_personalization: 'granted', // ❌ Same problem
  analytics_storage: 'granted'  // ❌ Same problem
});

2. Missing ad_user_data and ad_personalization

Many implementations from 2023 only set ad_storage and analytics_storage. That was Consent Mode v1. If you're missing the two new signals, Google treats your implementation as non-compliant. Both new signals must be present in both the default and update calls.

3. Consent defaults run after gtag.js loads

If gtag('consent', 'default', ...) runs after the Google tag script, the first page view has no consent context. Google tags fire once on load, and that first hit won't have your consent defaults applied. The default call must be the very first gtag call in the page, before any script tag that loads gtag.js or gtm.js.

4. Not handling banner dismissal

If a user closes the banner without clicking Accept or Reject, most implementations do nothing. Consent stays denied (which is correct), but the banner often reappears on every page, creating a poor UX. Worse: some implementations treat dismissal as acceptance.

Best practice: Treat dismissal as "denied". Suppress the banner for 24 hours (store a flag in localStorage). Show it again on the next visit. Never infer consent from inaction.

5. Missing geo-targeting

Consent Mode is only required for users in the EEA, UK, and Switzerland. For users outside these regions, you can default to granted (since there's no EU consent requirement). Google supports region parameter in the default call:

Geo-targeted consent defaults
gtag('consent', 'default', {
  ad_storage: 'denied',
  ad_user_data: 'denied',
  ad_personalization: 'denied',
  analytics_storage: 'denied',
  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', 'IS', 'LI', 'NO', 'CH'],
  wait_for_update: 500
});

// Users outside listed regions get full tracking by default
gtag('consent', 'default', {
  ad_storage: 'granted',
  ad_user_data: 'granted',
  ad_personalization: 'granted',
  analytics_storage: 'granted'
});
⚠️
Don't forget UK and EFTA

GB (United Kingdom), IS (Iceland), LI (Liechtenstein), NO (Norway), and CH (Switzerland) all have consent requirements. Many implementations only include EU-27 countries and miss these five. Post-Brexit UK still requires GDPR-equivalent consent under UK GDPR.

6. Not re-firing tags after consent update

When gtag('consent', 'update', ...) runs, Google tags that were blocked will automatically re-fire if they use wait_for_update. But if you're using a custom implementation that blocks script loading (not just consent signalling), you need to manually reload those scripts after consent is granted. Consent Mode is about signalling, not script blocking.

How CookieGuard automates verification

Manual verification with DevTools works, but it doesn't scale. You can't check every page, every deploy, every new tag your marketing team adds. CookieGuard's scanner validates Consent Mode v2 implementation automatically:

What CookieGuard checks

1
Consent defaults fire before any Google tag
Verifies that gtag('consent', 'default', ...) runs before gtag.js or gtm.js loads. Flags implementations where defaults run too late.
Critical
2
All four v2 signals are present
Checks that ad_storage, analytics_storage, ad_user_data, and ad_personalization are all declared. Missing signals = v1 implementation.
Critical
3
Default state is denied for EEA users
Confirms no signal defaults to granted for users in EU/EEA regions. Catches the opt-out anti-pattern.
High
4
Consent update fires on banner interaction
Simulates a consent accept and verifies that gtag('consent', 'update', ...) fires with the correct signal values.
High
5
No tracking cookies set before consent
Scans all Set-Cookie headers and client-side cookies on initial load. Flags _ga, _gid, _gcl_au, _fbp, and other tracking cookies that fire pre-consent.
Critical

Add it to your CI/CD pipeline and violations get caught before they ship. See our full compliance checklist for the GitHub Action setup.

Consent Mode v2 vs. blocking scripts entirely

A common question: why not just block all Google scripts until consent is granted? You can. It's simpler. But there's a trade-off.

With script blocking, you get zero analytics data from users who don't consent. In markets where consent rates are 30-50%, that's half your traffic invisible.

With Consent Mode, Google sends cookieless pings even when consent is denied. These pings don't set cookies and don't contain personally identifiable information. Google uses them for conversion modelling, which fills in estimated conversions from the unconsented traffic. This typically recovers 50-70% of the data you'd otherwise lose.

Approach Analytics Data (No Consent) GDPR Compliant? Complexity
Block all scripts Zero data from unconsented users Yes Low
Consent Mode v2 Cookieless pings + conversion modelling Yes (when defaulting to denied) Medium
Default granted (broken) Full data (but illegally collected) No Low

For most sites, Consent Mode v2 is the right balance: legally compliant, still useful data, and required by Google anyway.

Quick reference: the complete code

Here's the full implementation in one block. Copy-paste into your <head>, before any other scripts:

Complete Consent Mode v2 implementation
<!-- 1. Consent defaults (FIRST thing in <head>) -->
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){ dataLayer.push(arguments); }

  // Default: deny everything for EEA + UK + EFTA
  gtag('consent', 'default', {
    ad_storage: 'denied',
    ad_user_data: 'denied',
    ad_personalization: 'denied',
    analytics_storage: 'denied',
    wait_for_update: 500
  });

  // Restore previous consent if user already chose
  const stored = localStorage.getItem('cookie_consent');
  if (stored) {
    try {
      gtag('consent', 'update', JSON.parse(stored));
    } catch(e) {}
  }
</script>

<!-- 2. Load GA4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"></script>
<script>
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXX');
</script>

<!-- 3. Consent banner callbacks -->
<script>
  function acceptAll() {
    const consent = {
      ad_storage: 'granted',
      ad_user_data: 'granted',
      ad_personalization: 'granted',
      analytics_storage: 'granted'
    };
    gtag('consent', 'update', consent);
    localStorage.setItem('cookie_consent', JSON.stringify(consent));
    hideBanner();
  }

  function rejectAll() {
    const consent = {
      ad_storage: 'denied',
      ad_user_data: 'denied',
      ad_personalization: 'denied',
      analytics_storage: 'denied'
    };
    gtag('consent', 'update', consent);
    localStorage.setItem('cookie_consent', JSON.stringify(consent));
    hideBanner();
  }
</script>

Verify your implementation in 30 seconds

CookieGuard scans your live site, checks all four Consent Mode v2 signals, and flags issues. Free scan, no account.

Scan my site free →