Skip to content

Consent Handling in Server-Side GTM

Server-side GTM does not run in the user’s browser — it runs on your server infrastructure. This changes the consent architecture significantly. The browser still handles the CMP, the banner, and the user’s consent choice. But the consent signal needs to travel from the browser to your sGTM container, where it determines which vendor tags fire.

This architecture gives you something powerful: a central enforcement point for all outbound data requests, regardless of what client-side code does.

The consent state from the browser’s Consent Mode does not automatically propagate to sGTM. You need to explicitly forward it.

The most common setup: the browser sends events to sGTM via the GA4 client. When events arrive, the GA4 client parses the gcs parameter from the hit URL — the consent signal that GA4 automatically appends to every request.

The gcs parameter encodes the current consent state:

  • G111 = both ad_storage and analytics_storage granted
  • G100 = both denied
  • G101 = only analytics_storage granted
  • G110 = only ad_storage granted

The sGTM GA4 client makes this available in the event model. You can read it in server-side variables and use it to condition tag firing.

// sGTM Custom Variable — reads consent state from incoming GA4 hit
// Variable type: Custom JavaScript
const eventModel = require('getAllEventData');
function getConsentState() {
const consentParam = eventModel()['x-ga-gcs'];
// x-ga-gcs is how the consent signal appears in sGTM's event model
return consentParam || 'unknown';
}
return getConsentState();

A more reliable approach: explicitly include consent state as an event parameter in your dataLayer pushes. This travels through sGTM as a regular event parameter and is accessible via any variable type.

// Browser dataLayer push — include consent state explicitly
window.dataLayer.push({
'event': 'page_view',
'consent_analytics': analyticsGranted ? 'granted' : 'denied',
'consent_advertising': advertisingGranted ? 'granted' : 'denied'
});

In sGTM, read these with Event Data variables pointing to consent_analytics and consent_advertising.

Create reusable variables for consent state checks:

Variable: Consent - Analytics Storage

// Custom JavaScript variable in sGTM
const getAllEventData = require('getAllEventData');
function() {
const eventData = getAllEventData();
// Check explicit consent parameter first
if (eventData.consent_analytics) {
return eventData.consent_analytics;
}
// Fall back to decoding the gcs parameter
const gcs = eventData['x-ga-gcs'] || '';
// gcs format: G{ad_storage}{analytics_storage}
// Third character is analytics_storage: 1=granted, 0=denied
if (gcs.length >= 4) {
return gcs[3] === '1' ? 'granted' : 'denied';
}
return 'unknown';
}

Variable: Consent - Ad Storage

const getAllEventData = require('getAllEventData');
function() {
const eventData = getAllEventData();
if (eventData.consent_advertising) {
return eventData.consent_advertising;
}
const gcs = eventData['x-ga-gcs'] || '';
if (gcs.length >= 3) {
return gcs[2] === '1' ? 'granted' : 'denied';
}
return 'unknown';
}

With consent variables in place, use them in tag firing conditions.

Approach 1: Trigger conditions

In sGTM triggers, add a condition:

  • Variable: {{Consent - Analytics Storage}}
  • Condition: equals granted

Any tag on this trigger only fires when analytics storage is granted.

Approach 2: Tag configuration

Inside a Custom Tag template, check consent state before executing:

// Inside a server-side tag template
const getAllEventData = require('getAllEventData');
const logToConsole = require('logToConsole');
const sendHttpRequest = require('sendHttpRequest');
const eventData = getAllEventData();
const analyticsConsent = eventData['consent_analytics'];
const adConsent = eventData['consent_advertising'];
// Gate: only send to advertising vendor if consent granted
if (adConsent !== 'granted') {
logToConsole('Advertising consent not granted. Skipping vendor tag.');
data.gtmOnSuccess();
return;
}
// Proceed with vendor tag execution
const pixelUrl = buildPixelUrl(eventData);
sendHttpRequest(pixelUrl, function(statusCode) {
if (statusCode >= 200 && statusCode < 300) {
data.gtmOnSuccess();
} else {
data.gtmOnFailure();
}
}, {method: 'GET', timeout: 3000});

sGTM is the ideal place to strip PII from events before they reach third-party vendors. When consent for certain processing is denied, you can redact personal data fields before forwarding:

// sGTM Custom Tag — PII redaction layer
const getAllEventData = require('getAllEventData');
const setEventData = require('setEventData');
const logToConsole = require('logToConsole');
const eventData = getAllEventData();
// If advertising consent is denied, strip user identifiers
if (eventData.consent_advertising !== 'granted') {
// Remove email address
setEventData('user_data.email', null);
setEventData('user_data.email_address', null);
setEventData('user_data.sha256_email_address', null);
// Remove phone
setEventData('user_data.phone_number', null);
setEventData('user_data.sha256_phone_number', null);
// Remove address data
setEventData('user_data.address', null);
logToConsole('PII redacted: advertising consent not granted');
}
data.gtmOnSuccess();

Run this as a Setup Tag before any tags that might forward user data. This approach centralizes PII handling — you do not need to configure redaction in every individual vendor tag.

Client-side consent relies on each vendor’s JavaScript tag respecting the consent signal. If a vendor tag has a bug, is updated unexpectedly, or does not natively support Consent Mode, consent can be violated without you knowing.

sGTM reverses this dynamic. Your server-side container is the last point before data leaves your infrastructure and reaches vendor APIs. You control:

  • Which events reach which vendors
  • What data is included in each request
  • When to drop a request entirely based on consent

This centralization is the primary compliance advantage of server-side tagging beyond the performance and first-party data benefits.

Client-Side Consent Enforcement

Each vendor tag must respect Consent Mode signals independently.

A single misbehaving or outdated tag can violate consent.

Consent enforcement is distributed — no single control point.

Tag behavior depends on vendor implementation quality.

Server-Side Consent Enforcement

sGTM is a central gatekeeping layer before all vendor API calls.

You control what data leaves your server regardless of vendor behavior.

Single place to audit and enforce consent compliance.

PII redaction and consent gating apply to all downstream tags simultaneously.

When an event arrives at sGTM, the event model contains all event data including consent signals. Check available fields in the sGTM debugger (Preview mode):

Key fields to look for:

  • x-ga-gcs — the Google consent signal from GA4 hits
  • x-ga-rdp — restricted data processing flag
  • Any custom consent parameters you pass explicitly

In Preview mode, click any incoming request and check the Event Data panel to see the full event model. Consent-related fields will appear alongside your regular event parameters.

Assuming sGTM automatically knows about browser consent. sGTM receives no automatic consent signal unless you explicitly forward it from the browser. The consent state must travel as data in your events.

Only gating tags with trigger conditions but not redacting PII. A tag that fires with denied consent but strips PII is still firing when it shouldn’t. Use both gating (don’t fire) and redaction (strip data if you must fire for other reasons).

Not testing the full consent-denied path through sGTM. Browser testing shows the browser side. You must also verify in sGTM Preview mode that denied-consent events do not reach vendor tags. Check sGTM’s Preview mode independently from GTM’s browser Preview.

Relying on x-ga-gcs without validating it. The gcs parameter can be absent or malformed. Always have a fallback value and treat unknown as the most restrictive state.