Why the Two Commands Exist
Google Consent Mode operates on a simple contract: before any Google tag fires a network request, it must know what the user has (or hasn’t) consented to. Two gtag commands handle that lifecycle — 'consent', 'default' and 'consent', 'update' — and they are not interchangeable. Getting them in the wrong order, or calling either one at the wrong moment, silently poisons your measurement without throwing a single JavaScript error.
gtag(‘consent’, ‘default’, …)
The default call sets the pre-consent state. Think of it as the starting position before the user has done anything. It answers the question: “If I know nothing about this person’s preferences yet, what should every Google tag assume?”
Two non-negotiable rules apply:
- It must appear before any Google tag script loads. Specifically, it must execute before the
gtag.jsor GTM container script is added to the page. If a Google tag runs even one millisecond before the default call, that tag has already made its own default assumptions — and they are granted, not denied. - It fires exactly once per page load. Calling
defaulta second time after the container has loaded has no effect. The state is already set.
<!-- Correct: default fires BEFORE the GTM/gtag.js snippet -->
<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
});
</script>
<!-- GTM container loads AFTER -->
<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXX"></script>
The wait_for_update parameter (in milliseconds) tells Google tags to pause before sending pings while the page waits for the CMP to resolve. Without it, a slow-loading banner can allow tags to fire in the denied-but-not-yet-blocked window.
gtag(‘consent’, ‘update’, …)
The update call reflects the user’s actual choice. It fires after the consent banner interaction — accept, decline, or partial selection — and it only needs to include the parameters that changed. Google tags listen for this call and immediately adjust their behaviour for any queued or future requests.
<!-- Fired by your CMP callback after the user clicks Accept -->
gtag('consent', 'update', {
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
analytics_storage: 'granted'
});
On a returning visit where consent is already stored in a cookie or localStorage, your CMP should read that stored preference and fire update as early as possible — ideally before wait_for_update expires — so the tags don’t waste the pause window.
What a Compliant dataLayer Sequence Looks Like
The dataLayer is the source of truth. Here is what you should see when you run console.log(dataLayer) or inspect it in Tag Assistant on a first visit where the user accepts all categories:
[
// 1. Consent default — index 0, before GTM bootstraps
{
"0": "consent",
"1": "default",
"2": {
"ad_storage": "denied",
"ad_user_data": "denied",
"ad_personalization": "denied",
"analytics_storage": "denied",
"wait_for_update": 500
}
},
// 2. GTM bootstrap event (added automatically by the container)
{ "event": "gtm.js", "gtm.start": 1700000000000 },
// 3. Consent update — fired by CMP after user clicks Accept
{
"0": "consent",
"1": "update",
"2": {
"ad_storage": "granted",
"ad_user_data": "granted",
"ad_personalization": "granted",
"analytics_storage": "granted"
}
},
// 4. Any downstream dataLayer events (page_view, purchase, etc.)
{ "event": "page_view" }
]
The critical pattern: index 0 is always the default call. The GTM bootstrap event (gtm.js) comes after it. The update call comes after GTM has loaded but before your measurement events.
What Breaks When default Is Too Late
If the GTM snippet loads before the default call executes — a common mistake when teams drop the default inside a DOMContentLoaded listener or at the bottom of <body> — the following happens:
- GTM initialises with Google’s built-in fallback, which is granted for all parameters.
- Tags fire immediately, sending cookies and user identifiers before any consent exists.
- When the
defaultcall eventually runs, it is a no-op — the state is already committed. - The subsequent
updatecall downgrades nothing, because the initial pings have already left the browser.
This is precisely the scenario regulators flag when they examine network requests captured before a user has interacted with a banner. The fix is structural, not configurational: the default call must live in an inline <script> block that appears in the HTML above the GTM snippet, every single time.
Quick Reference
- default → runs once, before GTM/gtag.js, sets denied baseline, uses
wait_for_update. - update → runs after user interaction (or on return visits after reading stored consent), reflects actual choice, can be called multiple times if preferences change.
- Order in dataLayer: default → gtm.js bootstrap → update → measurement events.
- Symptom of late default: Tags visible in Network tab before banner interaction; dataLayer shows
gtm.jsbefore the consent object.