Skip to content

Cookie consent + GTM analytics gating

Make sure the consent banner only lets GA4/posthog (analytics) and factors.ai (marketing) run when the visitor explicitly opts in. The banner itself is provided by a library, so this document just covers what messages we need to send to the downstream vendors and how to wire GTM so no tracking fires when a category is unchecked.

Empowerment is key: the rest of this guide breaks the setup into precise, actionable steps so you stay in control of each vendor and avoid accidental data collection.

  • Essential: always on so GTM can load and the banner can function. No tracking tags should live in this bucket.
  • Analytics: powers GA4 and PostHog. Treat this as a single switch: deny the category and we must block both vendors; grant it and the tags can resume.
  • Marketing: powers factors.ai. Keep the marketing stack behind this toggle so we never burn marketing cookies unless visitors click that category.

The banner must push updates to GTM/the vendors any time consent changes so those services can stop/start immediately.

Have the banner library push a consent update event like this every time the user saves preferences:

window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'cookie_consent_updated',
consent: {
analytics: /* true when Analytics checked */,
marketing: /* true when Marketing checked */,
},
});

Also push the corresponding GTM consent message so GTM’s consent-aware tags respect the new state:

window.dataLayer.push({
'gtm.consentDenied': {
analytics_storage: !consent.analytics ? 'denied' : 'granted',
ad_storage: !consent.marketing ? 'denied' : 'granted',
},
});
window.dataLayer.push({
'gtm.consentGranted': {
analytics_storage: consent.analytics ? 'granted' : 'denied',
ad_storage: consent.marketing ? 'granted' : 'denied',
},
});

Keep the data-layer event names consistent so GTM triggers can listen for cookie_consent_updated and update variables.

  1. Enable Consent Overview inside GTM and set the default state of analytics_storage to denied.
  2. On your GA4 Configuration tag:
    • Require consent for analytics_storage.
    • Add a trigger that fires on the cookie_consent_updated event and ensure it only fires when the consent.analytics variable is true.
  3. When the banner pushes gtm.consentGranted with analytics_storage: 'granted', GTM will stop blocking GA4 and the tag can run immediately. When the user denies Analytics, GTM keeps the tag blocked because analytics_storage remains denied.
  1. Pick the Google Tag type and paste your G-XXXXXXX tag ID into the Tag ID field so the right property receives events.
  2. Leave Configuration settings and Shared event settings unset unless you have a shared variable that already preloads parameters; defaulting to “None” keeps things simple.
  3. Expand Advanced Settings and ensure the firing options stay at “Once per event” (the safest default) so tags respond predictably to each consent update.
  4. In the Consent Settings (BETA) section:
    • Enable the built-in checks shown (analytics_storage, ad_storage, ad_personalization, ad_user_data) so GTM can enforce the right storage block.
    • Set Additional consent checks to “No additional consent required” once you are sure the banner is already providing the consent signal (this avoids double blocking).
  5. Combine these tag-level settings with the cookie_consent_updated trigger logic described above so the tag only fires when consent is granted.

These steps keep you in the driver’s seat, letting GTM do exactly what you tell it—no hidden analytics when a visitor denies consent.

PostHog is currently used on the marketing site; make sure it is also behind the analytics consent:

  1. If PostHog is loaded via GTM, treat its tag like GA4: require analytics_storage consent and fire only when consent.analytics is true.
  2. If PostHog is loaded outside GTM (e.g., inline script), call posthog.opt_out_capturing() as soon as the banner says analytics is denied and posthog.opt_in_capturing() when granted.
  3. Use the same cookie_consent_updated event to keep PostHog in sync with GTM; for example:
    if (!consent.analytics) {
    posthog.opt_out_capturing();
    } else {
    posthog.opt_in_capturing();
    }
  1. Keep the factors.ai tag inside GTM and mark it as requiring ad_storage consent (marketing category).
  2. Add a trigger that fires only when the consent.marketing flag from the cookie_consent_updated event is true.
  3. If GTM’s marketing consent is denied, GTM will keep the marketing tag from firing until the visitor opts in.

If managing factors.ai inside GTM proves unwieldy, it is acceptable to load it elsewhere, but the loading must still be gated by the same marketing consent signal (e.g., only append the script tag when consent.marketing becomes true).

You can keep GA4, PostHog, and factors.ai inside GTM as long as:

  • Each tag is consent-aware (Require Consent toggles enabled).
  • Triggers depend on the cookie_consent_updated event and the matching consent variable.
  • The banner updates gtm.consentGranted/Denied so GTM’s consent settings react in real time.

If a vendor cannot be made GTM-consent-aware, migrate it out of GTM and load it manually after the banner confirms consent. The important part is blocking network calls until consent is granted, regardless of whether GTM orchestrates the loading.

  • Banner pushes cookie_consent_updated + gtm.consentGranted/Denied every time preferences change.
  • GA4 tag requires analytics_storage consent and fires only when analytics is granted.
  • PostHog opt-out/in logic mirrors the analytics consent state.
  • factors.ai tag requires ad_storage consent and respects the marketing toggle.
  • No tracking requests are made unless the banner’s marketing/analytics boxes are checked.