Consent Initialization Timing
The most common Consent Mode implementation error is invisible in normal testing: the default consent state is set after GTM has already started evaluating tags. Tags that evaluate consent state during this window see an undefined value and may behave unexpectedly — either firing without consent or blocking themselves unnecessarily.
Understanding GTM’s initialization order is the key to getting consent timing right.
GTM’s three initialization stages
Section titled “GTM’s three initialization stages”GTM fires events in a specific order on every page load. The order is:
- Consent Initialization — All Pages: fires first, before any container configuration is applied
- Initialization — All Pages: fires second, before page view events
- All Pages (Page View): fires third, your normal tag triggers
Tags on the Consent Initialization trigger fire before anything else in GTM. This is precisely why it exists — to give you a place to establish consent state before any tag reads it.
The triggers are visible in GTM when you create a trigger: select “Page View” as the trigger type, then choose “Consent Initialization - All Pages” or “Initialization - All Pages” from the “When should this trigger fire” dropdown.
The “consent read before default set” error
Section titled “The “consent read before default set” error”GA4’s DebugView sometimes shows a warning: “A tag read consent state before a default was set.” This warning means exactly what it says — a tag evaluated its consent requirements before any gtag('consent', 'default') call had been processed.
When this happens, Google’s tags treat the consent state as if all types are granted. This is the opposite of what you want for EU compliance.
What causes it:
- No
gtag('consent', 'default')call exists anywhere in the page - The
gtag('consent', 'default')call exists but comes after the GTM snippet - The default is configured inside a GTM tag that fires on All Pages, not Consent Initialization
How to diagnose it:
In GTM Preview mode, open the first event in the timeline (gtm.init_consent). Go to the Consent tab. If any consent type shows undefined rather than granted or denied, the default was not set before this stage.
Then check the gtm.init event. If the defaults appear here (not at gtm.init_consent), your default-setting tag is on Initialization, not Consent Initialization — which is too late for some tag types.
The correct sequence
Section titled “The correct sequence”Page HTML starts parsing │ ▼<script> — gtag('consent', 'default', {...}) ←── SET DEFAULT HERE │ ▼<script> — GTM snippet loads │ ▼GTM container JS downloads (async) │ ▼[gtm.init_consent event fires] │ ▼Consent Initialization tags fire (CMP integration tags) │ ▼[gtm.init event fires] │ ▼Initialization tags fire │ ▼[gtm.js / Page View event fires] │ ▼All Pages tags fire (GA4, Ads, etc.)The safest implementation: set the default state in an inline <script> block before the GTM snippet. This guarantees the default is processed before GTM loads, with no dependence on GTM’s trigger system at all.
Implementation patterns
Section titled “Implementation patterns”Pattern 1: Inline script (recommended)
Section titled “Pattern 1: Inline script (recommended)”<head> <!-- Consent default — BEFORE GTM snippet --> <script> 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', 'security_storage': 'granted', 'wait_for_update': 500 }); </script>
<!-- GTM snippet immediately after --> <script> (function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXX'); </script></head>This pattern is immune to any GTM trigger timing issues. The default is established by the browser’s JavaScript engine before GTM’s network request has even been made.
Pattern 2: Consent Initialization trigger in GTM
Section titled “Pattern 2: Consent Initialization trigger in GTM”If you cannot inject inline scripts (certain CMS platforms, strict CSP policies), use the Consent Initialization trigger:
- Create a Custom HTML tag with the consent default code
- Set the trigger to Consent Initialization — All Pages
- Ensure no other tags fire on Consent Initialization before this one (set firing order to priority 999 if needed)
<!-- GTM tag code — fires on Consent Initialization --><script> 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', 'security_storage': 'granted', 'wait_for_update': 500 });</script>Pattern 3: Google Tag configuration (not recommended)
Section titled “Pattern 3: Google Tag configuration (not recommended)”Some guides suggest configuring consent defaults in the Google Tag’s settings. Do not use this approach. The Google Tag fires on All Pages — two stages after Consent Initialization. Any tag that evaluates consent before the Google Tag fires will see an undefined consent state.
CMP timing and wait_for_update
Section titled “CMP timing and wait_for_update”Even with the correct default state, there is a timing gap between:
- GTM starting to evaluate tags
- The CMP loading and calling
gtag('consent', 'update', {...})
The wait_for_update parameter creates a pause in this window. During the pause, Google’s tags queue their consent checks. When the update arrives, they re-evaluate.
Verifying wait_for_update is working
Section titled “Verifying wait_for_update is working”In GTM Preview mode:
- Load your site in a fresh incognito browser connected to Preview mode
- Look at the event timeline — find the
gtm.init_consentevent and note its timestamp - Find the consent update event (from your CMP callback) and note its timestamp
- The gap between these two timestamps is how long your tags are waiting
- Your
wait_for_updatevalue should be larger than this gap
If the gap exceeds your wait_for_update value, tags fire with the default denied state before the CMP updates. Increase wait_for_update or investigate why your CMP takes so long to fire its callback.
What happens when tags fire before the consent update
Section titled “What happens when tags fire before the consent update”If a GA4 event tag fires before the consent update arrives:
- The tag checks
analytics_storageand finds itdenied - The tag does not fire (in Basic mode) or fires as a cookieless ping (in Advanced mode)
- The user’s consent choice — even if they accepted — does not retroactively apply to this missed firing
- The event is simply lost
This is most visible on the first page view: if the consent update for a returning visitor arrives late, the pageview event fires before consent is established, and even though consent was granted (stored from a previous visit), the pageview data is lost.
The fix: ensure your CMP reads and fires the stored consent callback as early as possible on page load, ideally synchronously or within the first 200ms of page execution.
Checking timing in Preview mode: a complete walkthrough
Section titled “Checking timing in Preview mode: a complete walkthrough”-
Open GTM Preview mode
-
Load your site in a fresh incognito browser (important — no stored consent)
-
Do not interact with the consent banner
-
In the Preview panel, click on
gtm.init_consent -
Go to the Consent tab: all types should show
denied -
Go to the Tags tab: no tags should have fired yet
-
Click on
gtm.init -
Go to the Consent tab: still
denied(CMP hasn’t fired yet) -
Click on
gtm.js -
If your CMP integration tag is correctly on Consent Initialization, it should appear in the Tags tab as Fired
-
Find the first event after the CMP callback (look for your CMP’s custom event)
-
Go to its Consent tab: should show
grantedfor types the user accepted (in this case nobody accepted, so should showdenied) -
Now interact with the banner — click Accept All
-
Find the consent update event in the timeline
-
All subsequent events should have
grantedin their Consent tab
Common mistakes
Section titled “Common mistakes”Using All Pages trigger for consent default. This fires at the third stage, not the first. Tags on Consent Initialization (like GA4) may have already evaluated consent by then.
Forgetting to account for the Google Tag’s built-in consent checking. The Google Tag (GA4 configuration) has its own consent mechanism. Even if you gate GA4 event tags on consent, the Google Tag itself must be properly configured or it fires at the wrong consent stage.
Not testing consent timing with a slow network. On slow connections, the CMP may take longer to load, pushing the consent update past the wait_for_update timeout. Test with DevTools Network throttling set to “Slow 3G.”
Using gtag before defining it. If your gtag('consent', 'default') call runs before function gtag(){dataLayer.push(arguments);}, the call fails silently and the default is never set. Always define gtag before calling it.