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.
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 |
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
This must run in the <head>, before the gtag.js script tag. If it runs after, the first page view fires without consent context.
<!-- 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>
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.
// User clicked "Accept All" gtag('consent', 'update', { ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted', analytics_storage: 'granted' });
// User clicked "Accept Analytics Only" gtag('consent', 'update', { ad_storage: 'denied', ad_user_data: 'denied', ad_personalization: 'denied', analytics_storage: 'granted' });
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.
// 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
In your GTM container, go to Admin → Container Settings → Enable Consent Overview. This unlocks consent configuration in all tag settings.
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.
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.
Your Consent Management Platform (or custom banner) should push consent updates to the dataLayer. GTM listens for these events automatically:
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'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)
- Open your site in Chrome. Open DevTools (
F12). - Go to the Network tab. Filter by
googleorcollect. - Load the page without accepting cookies.
- Find the
collect?v=2request (GA4 measurement protocol hit). - 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=G1prefix confirms Consent Mode is active
- Now accept cookies and check the next request.
gcs=should change to reflect the granted state.
2. Google Tag Assistant (most detailed)
- Install the Tag Assistant Companion Chrome extension.
- Open Tag Assistant and connect to your site.
- Look for the Consent tab in the event timeline. It shows the consent state at each tag fire.
- Verify that the initial state shows all four signals as
denied. - Accept cookies and confirm the signals update to
granted.
3. GA4 DebugView (most complete)
- Enable GA4 DebugView: add
?debug_mode=trueto your URL or setdebug_mode: truein your gtag config. - Open GA4 → Admin → DebugView.
- 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.
Common mistakes (and how to fix them)
1. Defaulting to granted
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.
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:
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' });
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
gtag('consent', 'default', ...) runs before gtag.js or gtm.js loads. Flags implementations where defaults run too late.ad_storage, analytics_storage, ad_user_data, and ad_personalization are all declared. Missing signals = v1 implementation.denied for EEA usersgranted for users in EU/EEA regions. Catches the opt-out anti-pattern.gtag('consent', 'update', ...) fires with the correct signal values.Set-Cookie headers and client-side cookies on initial load. Flags _ga, _gid, _gcl_au, _fbp, and other tracking cookies that fire pre-consent.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:
<!-- 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.