GTM & Google Consent Mode v2
Google Consent Mode v2 lets you tell Google's tags how to behave when a visitor hasn't consented to tracking. Consenti pushes the right signals to your dataLayer automatically — you just configure a container ID and GTM handles the rest.
Why this matters
Without Consent Mode, Google Ads and Analytics tags fire unconditionally. With Consent Mode v2, they receive a consent state (granted or denied) and adjust their behaviour — firing in cookieless mode or not firing at all. This is required for EU compliance from March 2024 for conversion modelling in Google Ads.
Step 1 — Add GTM config to Consenti
import { ConsentiSetup } from '@consenti/ui'
new ConsentiSetup({
compliance: { type: 'opt-in' },
utils: {
gtm: {
containerId: 'GTM-XXXXXX', // your GTM container ID
urlPassthrough: true, // preserve click IDs (gclid/fbclid) for cookieless modelling
adsDataRedaction: true, // redact ad click IDs when ad_storage is denied
},
},
})That's it. Consenti injects the GTM snippet, calls gtag('consent', 'default', …) immediately with a denied-by-default state, then calls gtag('consent', 'update', …) each time the visitor submits consent.
<script>tag if you're using Consenti's GTM integration. Consenti injects the GTM snippet itself to ensure the consent default state is set before any tags fire. Injecting it twice creates duplicate pageview events.What gets pushed to dataLayer
Consenti calls the real gtag() consent API — via the standard stub-queue pattern, so it works whether your own gtag.js/GTM snippet loads before or after Consenti. Each call ultimately becomes a dataLayer.push([...]) entry:
// On page load — before visitor interaction
gtag('consent', 'default', {
analytics_storage: 'denied',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
functionality_storage: 'granted',
personalization_storage: 'denied',
security_storage: 'granted',
})After the visitor submits their preferences:
// On consent submission
gtag('consent', 'update', {
analytics_storage: 'granted',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
functionality_storage: 'granted',
personalization_storage: 'denied',
security_storage: 'granted',
})
// Plus, when configured:
gtag('set', 'url_passthrough', true)
gtag('set', 'ads_data_redaction', true) // true when ad_storage is deniedutils.gtm.verbose: true to additionally mirror every consenti:* event (banner shown, modal opened, etc.) onto the dataLayer as a generic { event, content }push — useful for custom, non-Consent-Mode GTM triggers. It's off by default so the dataLayer only carries real consent signals.Reading GTM-format consent in your code
Use widget.getGTMConsent() to read the current consent state in the exact format GTM expects — useful for debugging or for sending to your own analytics endpoint.
const widget = new ConsentiSetup({ /* ... */ })
const gtmConsent = widget.getGTMConsent()
// {
// analytics_storage: 'granted',
// ad_storage: 'denied',
// ad_user_data: 'denied',
// ad_personalization: 'denied',
// functionality_storage: 'granted',
// }Verifying in GTM Preview
- Open your site with GTM Preview active (click “Preview” in GTM).
- Reload the page. In the GTM debug panel, you should see a
consent_defaultevent with all valuesdenied. - Accept cookies on the banner. You should see a
consent_updateevent with the values reflecting your choices. - Check that tags configured to fire “On Consent” now fire (e.g. GA4 config tag).
collect to see GA4 hits. A hit with gcs=G111 means analytics consent was granted; gcs=G100 means denied.