Skip to content

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 fires events in a specific order on every page load. The order is:

  1. Consent Initialization — All Pages: fires first, before any container configuration is applied
  2. Initialization — All Pages: fires second, before page view events
  3. 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.

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.

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.

<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.

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:

  1. Create a Custom HTML tag with the consent default code
  2. Set the trigger to Consent Initialization — All Pages
  3. 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>
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.

Even with the correct default state, there is a timing gap between:

  1. GTM starting to evaluate tags
  2. 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.

In GTM Preview mode:

  1. Load your site in a fresh incognito browser connected to Preview mode
  2. Look at the event timeline — find the gtm.init_consent event and note its timestamp
  3. Find the consent update event (from your CMP callback) and note its timestamp
  4. The gap between these two timestamps is how long your tags are waiting
  5. Your wait_for_update value 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.

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_storage and finds it denied
  • 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”
  1. Open GTM Preview mode

  2. Load your site in a fresh incognito browser (important — no stored consent)

  3. Do not interact with the consent banner

  4. In the Preview panel, click on gtm.init_consent

  5. Go to the Consent tab: all types should show denied

  6. Go to the Tags tab: no tags should have fired yet

  7. Click on gtm.init

  8. Go to the Consent tab: still denied (CMP hasn’t fired yet)

  9. Click on gtm.js

  10. If your CMP integration tag is correctly on Consent Initialization, it should appear in the Tags tab as Fired

  11. Find the first event after the CMP callback (look for your CMP’s custom event)

  12. Go to its Consent tab: should show granted for types the user accepted (in this case nobody accepted, so should show denied)

  13. Now interact with the banner — click Accept All

  14. Find the consent update event in the timeline

  15. All subsequent events should have granted in their Consent tab

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.