Consent Mode v2 debugging guide

last verified · against Google Consent Mode v2 docs as of 2026-07

Field reference for Consent Mode v2 debugging: decode gcs and gcd signals, verify default and update in Tag Assistant, and fix the common failures.

What this is

Consent Mode v2 debugging is the task of confirming that a page sends the four consent signals — ad_storage, analytics_storage, ad_user_data, ad_personalization — in the correct default-then-update order before any Google tag fires, and reads them back from the network request or Tag Assistant. This card maps each signal, network parameter, and Tag Assistant surface to the misconfiguration it exposes and the fix.

The four signals to verify

Signal Sets consent for Version Value
ad_storage Storage of advertising cookies / device identifiers v1 'granted' or 'denied'
analytics_storage Storage of analytics cookies v1 'granted' or 'denied'
ad_user_data Sending user data related to advertising to Google; required for enhanced conversions and tag-based conversion tracking added in v2 'granted' or 'denied'
ad_personalization Personalized advertising added in v2 'granted' or 'denied'

By default, no consent mode values are set — unset is not the same as 'denied'. If nothing is configured, GA4 treats ad_storage and analytics_storage as 'granted', so data still flows and masks the misconfiguration.

Type Group
ad_storage, analytics_storage, ad_user_data, ad_personalization Core advertising / analytics signals — what v2 debugging targets
functionality_storage, personalization_storage, security_storage Other privacy storage types, not the four core signals

What each network parameter carries

Parameter Encodes Always sent? Client key Server-side key
gcs The ad_storage and analytics_storage consent choice only No — reflects consent-mode state when tags fire gcs x-ga-gcs in Event Data
gcd All four signals plus how each signal was generated (default vs update) Yes — sent to Google services whether or not consent mode is active gcd Not specified in the sources

Because gcd is always sent, its presence alone is not proof consent mode is configured. Use an explicit default Consent event (and non-l gcd letters) as the real test. Because gcs carries only two of the four signals, a “green” gcs can hide missing ad_user_data / ad_personalization.

gcs value decode (community-documented)

Google does not publish a gcs value table; it confirms only that gcs transmits ad_storage and analytics_storage. The mapping below is community-documented (CookieHub, dumbdata, owntag, Simo Ahava). Verify live against a known consent state.

Format G1xy: x = ad_storage, y = analytics_storage, where 1 is granted and 0 is denied.

gcs ad_storage analytics_storage Reading
G100 denied denied Both denied (advanced mode, no update fired)
G110 granted denied ad_storage granted, analytics_storage denied
G101 denied granted ad_storage denied, analytics_storage granted
G111 granted granted Both granted

Unverified: a dashed / partial gcs (reported as G1--) is claimed by some practitioners to mean “no default set / consent mode not configured,” but no official page confirms it. The leading digit is described in community sources as 1 = signal sent; alternate leading digits are not officially documented.

gcd letter decode (community-documented)

Google does not publish the gcd letter table either; the letters are reverse-engineered by practitioners. The four letters appear in the order ad_storage, analytics_storage, ad_user_data, ad_personalization.

Letter State
l Signal not set with consent mode
p Denied by default (no update)
q Denied both by default and after update
r Denied by default, granted after update
t Granted by default (no update)
u Granted by default, denied after update
v Granted both by default and after update
m Denied after update (no default)
n Granted after update (no default)

Sources disagree on some letters: an alternate mapping in some references reads q as denied-then-granted, r as granted-then-denied, and adds s for granted-both. Treat exact letter semantics as approximate and verify live against a known consent state.

gcd string layout (community-documented, structure not published)
gcd = 11 [ad_storage] 1 [analytics_storage] 1 [ad_user_data] 1 [ad_personalization] 5
example: 11l1p1u1v5 (starts 11, a 1 separates each signal, terminator digit 5)
State GA4 behavior
No defaults ever set Treated as ad_storage: 'granted' and analytics_storage: 'granted' — data flows, hiding the missing config
analytics_storage: 'denied' Will not read or write first-party analytics cookies; cookieless pings are sent for future measurement / modeling
ad_storage: 'denied' with ads_data_redaction: 'true' GCLID / DCLID in consent and key-event pings are redacted

Basic vs advanced implementation

Knowing which mode the CMP deployed decides what “correct” looks like before consent.

Basic Advanced
Tags before consent Blocked until banner interaction Load on page open with defaults (denied unless configured)
Data before consent None sent — not even the default consent status Consent state plus cookieless pings
Expected pre-consent request No request (this is expected, not a bug) Cookieless ping carrying gcs=G100 (community-asserted; not stated in official docs)

Required command order

The single most common failure. If the consent code runs out of order, defaults do not work.

Order Action
1 Load the Google tag with the default consent call
2 Load the consent solution (CMP)
3 Call update after the user consents

For gtag, the gtag('consent','default') / gtag('consent','update') commands must fire before any gtag('config') command. wait_for_update (illustrated as 500 ms; no documented default or maximum) holds tags so an async CMP can send its update first.

Region-scoped defaults

Behavior Rule
Scope The region key limits a default to listed regions / subregions
Precedence Most specific parameter takes precedence over a broader default
Failure mode A region-scoped 'denied' default can apply to users the banner never prompts — align banner and default to the same regions

Tag Assistant surfaces

Tag Assistant (tagassistant.google.com, with the Companion Chrome extension) is Google’s sanctioned tool.

Check Event to select Where to read Expected
Default state Earliest Consent event in the Summary API Call section, or the On-page Default column Denied, all four parameters present
Update state Most recent Consent event in the Summary API Call section, or the On-page Update column Granted, all four parameters moved
Tag Assistant message Meaning
“A tag read consent state before a default was set” Ordering bug — a tag read consent before any default was set. Move the default commands above every tag snippet
Empty Consent tab Consent mode is not implemented on the page, or the Google tag was blocked from loading

For measurement that looks correct on-page but still excludes EEA users, check the server-side surface: GA4 Admin > Consent settings, per data stream (Advertising related consent signals, Behavior analytics consent signals). Notifications there may lag on-page signals by 48-72 hours.

Gotchas

  1. gcs stays G100 after “Accept all,” and data remains modeled / low → the banner’s update handler never fires gtag('consent','update',...), so state never leaves its default → select the most recent Consent event in Tag Assistant; if the four signals did not move to granted, wire the accept action to the update command (or, for a CMP, confirm Google consent mode is enabled).
  2. Tag Assistant shows “A tag read consent state before a default was set” → the default runs after a tag already read consent — an async CMP, or the default sits below the tag snippet / below gtag('config') → move every default command above all tag snippets, do not load it async, and add wait_for_update (e.g. 500) so an async banner’s update can land first.
  3. gcs is missing / empty in the network request → consent mode is not firing before Google’s tags, OR the site runs basic mode (no request before consent), OR the Google tag is blocked from loading → an empty Tag Assistant Consent tab means not-implemented or tag-blocked; confirm the tag loads, a default precedes it, and identify basic (expect no request) vs advanced (expect a gcs=G100 cookieless ping).
  4. EEA users are silently denied / low consent rate without ever seeing a banner → region mismatch — the banner and the region-scoped default target different regions → align both to the same regions, audit overlapping region defaults (most specific wins), and test with Chrome location simulation.
  5. Remarketing / enhanced conversions / audiences stop for EEA users even though gcs looks finead_user_data and/or ad_personalization were never set; gcs carries only ad_storage + analytics_storage, so a green gcs hides them → check the gcd string (or Tag Assistant API Call) for all four, set defaults and updates for the two v2 signals, and verify GA4 Admin > Consent settings.
  6. In server-side GTM you filter for gcs and find nothing → sGTM renames the field to x-ga-gcs in Event Data → read x-ga-gcs, then apply the same decode logic.
  7. A site with no consent mode still reports GA4 data normally, so nobody notices → with defaults never set, GA4 treats both storage signals as granted → do not treat “data is flowing” as proof; verify an explicit default Consent event (earliest event) with all four parameters set.
  8. Consent mode looks correct but Google Ads shows no impact / modeling results → data thresholds and minimum run time are not met yet — not an implementation bug → keep the implementation running at least 7 full days and meet the data thresholds; do not “fix” a working setup.

Quick recipes

consent-default.js — must run before any tag or gtag('config')
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('consent', 'default', {
ad_storage: 'denied',
analytics_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
wait_for_update: 500, // ms to let an async CMP send its update first
});
consent-update.js — fire on banner accept
gtag('consent', 'update', {
ad_storage: 'granted',
analytics_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
});
region-default.js — region-scoped default, most specific wins
// Narrow default, scoped to two regions...
gtag('consent', 'default', {
analytics_storage: 'denied',
region: ['ES', 'US-AK'],
});
// ...and a broader default for everywhere else.
gtag('consent', 'default', {
ad_storage: 'denied',
});
// Most specific parameter takes precedence.
decode-gcs.js — run in the DevTools console
// Paste a captured Google request URL (the collect / Ads request).
const url = new URL('PASTE_REQUEST_URL');
const gcs = url.searchParams.get('gcs'); // e.g. "G111"
// Community decode of G1xy (Google does not publish this table):
console.log({
gcs,
ad_storage: gcs && gcs[2] === '1' ? 'granted' : 'denied',
analytics_storage: gcs && gcs[3] === '1' ? 'granted' : 'denied',
});
sgtm-read-gcs.js — server-side GTM variable
// In server-side GTM the signal is exposed as x-ga-gcs, not gcs.
const getEventData = require('getEventData');
return getEventData('x-ga-gcs'); // e.g. "G111"
redact-ads.js — redact ad-click IDs when ad_storage is denied
// With ad_storage denied, GCLID / DCLID in consent and key-event pings are redacted.
gtag('set', 'ads_data_redaction', true);

Sources

Community references (unofficial gcs / gcd decodings, not attributable to Google):

Changelog

  • — Initial version, verified against official documentation.

dataLayer

0 events · 0 sent

    • home /
      writing /writing
      guides /guides
      work /#work
      about /about
      colophon /colophon
      toggle analyst mode ctrl+.
      print session receipt /#receipt