Server-side GTM consent forwarding patterns

last verified · against Google Tag Platform consent and sGTM docs as of 2026-07

Reference for server-side GTM consent forwarding: gcs and gcd HTTP parameters, x-ga event-data keys, native versus custom tag gating, and denied-state behavior.

What this is

Server-side consent forwarding is the mechanism by which a website’s Consent Mode state travels from the browser to a server-side Tag Manager container as HTTP request parameters, where it becomes event-data keys that tags read to adjust or suppress the data they send. Native Google server tags honor the forwarded state automatically; custom and non-Google server tags must be gated manually, because the server-side sandbox exposes no consent API.

Reference tables

Every type carries a state of granted or denied. The cited sources describe four of the seven in detail.

Consent type Purpose (per cited docs)
ad_storage Advertising storage such as cookies
ad_user_data Consent to send user data to Google for online advertising
ad_personalization Consent for personalized advertising
analytics_storage Analytics storage
functionality_storage Not described in the cited sources
personalization_storage Not described in the cited sources
security_storage Not described in the cited sources
Step Where What happens
1 Browser Consent banner sends the user’s choices to the Google tag
2 Browser to server Google tag adds consent parameters to the HTTP request to the server container
3 Server container Google product tags are consent-aware and adjust the amount and kind of data they send
Parameter Carries Always sent?
gcs ad_storage plus analytics_storage consent Only with consent mode active
gcd Detailed consent across all consent types Always, even without consent mode
dma Consent HTTP parameter; not described in the cited sources Not specified
dma_cps Google-services (CPS) / ad-partner selection consent Not specified

Server-side event-data keys (internal parameters)

Event-data key Type Products
x-ga-gcs string Google Analytics
x-ga-gcd string Google Analytics, Google Ads, Floodlight
x-ga-dma boolean Google Analytics
x-ga-dma_cps string Google Analytics
x-ga-npa string Google Analytics

Mapping: the outgoing gcs reads as x-ga-gcs in event data; gcd as x-ga-gcd; dma as x-ga-dma; dma_cps as x-ga-dma_cps. The internal-parameters reference publishes only Name, Type, and Products (no value decoding) and warns that names, types, and products are subject to change.

Value-decoding note: Google does not publish the bit-level structure of gcs (the community G1XY scheme) or the base64 layout of gcd. Any value-to-category mapping is community reverse-engineering and may change without notice; treat it as unofficial.

Server tag Denied signal Behavior
GA4 analytics_storage Cookieless measurement (advanced mode) or fully blocked (basic mode)
Google Ads Conversions ad_storage No Ads cookies read or written; cookieless conversion pixel sent to a domain without third-party cookies
Google Ads Remarketing ad_storage Blocks the HTTP request and cookie use
Floodlight ad_storage Blocks the HTTP request and cookie use
Aspect Basic Advanced
Tag load Blocked until banner interaction Load on page open (defaults typically denied)
Pre-consent data Nothing sent, not even consent status Cookieless pings sent while denied
What the server sees pre-consent Few or no events Cookieless pings it can process and model
Modeling General conversion model Advertiser-specific model
API Web / client template Server template
setDefaultConsentState(consentSettings) Yes (write) Absent
updateConsentState(consentSettings) Yes (write) Absent
isConsentGranted(consentType) Yes (read) Absent
addConsentListener(consentType, listener) Yes (read) Absent
getConsentState Does not exist Absent
getEventData(keyPath) / getAllEventData() Not applicable Yes, read the forwarded signal
getRequestHeader / getRequestQueryParameter(s) / getRequestBody / getCookieValues Not applicable Yes

Notes: read APIs require the access_consent permission with read access; setDefaultConsentState and updateConsentState require access_consent write access. isConsentGranted returns true when the type is granted OR unset. setDefaultConsentState optional fields are region (array of ISO 3166-2 codes) and wait_for_update (milliseconds). The listener in addConsentListener is invoked with (consentType, granted).

Web-side knobs that shape the payload before it leaves the browser

Setting Effect when ad_storage denied Scope
ads_data_redaction=true Redacts ad-click identifiers (GCLID / DCLID) in pings and drops third-party cookies Web / client (gtag)
url_passthrough=true Passes ad-click info (e.g. gclid) via URL parameters across pages Web / client

Server-side consent design must assume the incoming payload was already shaped by these; they are not server-container settings.

Control Meaning
Built-in consent checks Template is itself consent-aware; Google tags check analytics_storage, ad_storage, ad_user_data, ad_personalization
Additional consent checks Tag fires only if all listed consent types are granted

Gotchas

  1. Server tags send full data despite on-site denial. Cause: the consent state never reached the server, either because the web Google tag is not forwarding gcs/gcd, or, for custom tags, because nothing in the container reads it. Fix: confirm the web Google tag forwards gcs/gcd to the container URL; for non-Google tags, read consent from event data and gate manually.
  2. A server variable referencing gcs returns undefined or empty. Cause: in event data the key is namespaced x-ga-gcs; the raw gcs name exists only as the outgoing HTTP parameter. Fix: reference x-ga-gcs (and x-ga-gcd) as the event-data key.
  3. Calling isConsentGranted() or addConsentListener() in a server template fails. Cause: those consent APIs exist only in web and client templates; the server sandbox exposes none. Fix: derive consent from request or event data (parse x-ga-gcs, or read a custom header or cookie) and implement gating manually.
  4. Some events arrive with no gcs or consent signal at all. Cause: the source is not running Google consent mode (custom clients, Measurement Protocol, non-consent-mode sites), or the site is on basic mode where nothing is sent pre-interaction. Fix: handle the missing case explicitly with a deliberate fallback, treating it as denied for safety or as configured.
  5. isConsentGranted() returns true although consent was never set. Cause: it returns true when the type is granted OR unset. Fix: always call setDefaultConsentState with explicit denied defaults before the user interacts, so an unset type does not read as granted.
  6. Reporting or attribution looks degraded or modeled after enabling consent mode. Cause: denied storage means cookieless pings, and Google fills gaps via modeling that requires data thresholds. Fix: this is expected behavior, not a bug; ensure advanced mode is configured, confirm traffic meets thresholds, and communicate the modeled nature of the numbers to stakeholders.

Quick recipes

read-forwarded-consent.js
const getAllEventData = require('getAllEventData');
const event = getAllEventData();
const gcs = event['x-ga-gcs']; // outgoing gcs, namespaced in event data
const gcd = event['x-ga-gcd']; // fuller picture incl. ad_user_data, ad_personalization
const dma = event['x-ga-dma']; // boolean
gate-custom-tag.js
const getEventData = require('getEventData');
// The server sandbox has no isConsentGranted() — read the signal yourself.
const gcs = getEventData('x-ga-gcs');
// No signal (Measurement Protocol, non-consent-mode source): fail closed.
// Treat as denied and do not send the outbound request.
if (gcs === undefined) {
// signal completion without firing, then stop
}
// Otherwise map gcs to a category via a configured lookup (value decoding is
// unofficial), and send the outbound request only when consent is granted.
default-consent-denied.js
const setDefaultConsentState = require('setDefaultConsentState');
setDefaultConsentState({
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
wait_for_update: 500, // milliseconds to wait for an update
region: ['ES', 'US-AK'] // optional ISO 3166-2 scoping
});
update-consent-granted.js
const updateConsentState = require('updateConsentState');
updateConsentState({
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
analytics_storage: 'granted'
});
check-before-fire.js
const isConsentGranted = require('isConsentGranted');
// Returns true if granted OR unset — set denied defaults first so
// an "unset" type cannot read as granted.
if (isConsentGranted('analytics_storage')) {
// fire the analytics tag
}
listen-for-updates.js
const addConsentListener = require('addConsentListener');
addConsentListener('ad_storage', (consentType, granted) => {
// re-evaluate gating when the user changes the choice
});

Sources

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