Consenti

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

consent.ts
typescript
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.

⚠️Do not add GTM via a separate <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:

javascript
// 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:

javascript
// 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 denied
ℹ️Set utils.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.

typescript
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

  1. Open your site with GTM Preview active (click “Preview” in GTM).
  2. Reload the page. In the GTM debug panel, you should see a consent_default event with all values denied.
  3. Accept cookies on the banner. You should see a consent_update event with the values reflecting your choices.
  4. Check that tags configured to fire “On Consent” now fire (e.g. GA4 config tag).
💡In Chrome DevTools → Network, filter for collect to see GA4 hits. A hit with gcs=G111 means analytics consent was granted; gcs=G100 means denied.

Frequently asked questions