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.
Consent categories
Section titled “Consent categories”- 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.
Data-layer contract
Section titled “Data-layer contract”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.
GA4 (Analytics) inside GTM
Section titled “GA4 (Analytics) inside GTM”- Enable Consent Overview inside GTM and set the default state of
analytics_storagetodenied. - On your GA4 Configuration tag:
- Require consent for
analytics_storage. - Add a trigger that fires on the
cookie_consent_updatedevent and ensure it only fires when theconsent.analyticsvariable is true.
- Require consent for
- When the banner pushes
gtm.consentGrantedwithanalytics_storage: 'granted', GTM will stop blocking GA4 and the tag can run immediately. When the user denies Analytics, GTM keeps the tag blocked becauseanalytics_storageremainsdenied.
How to wire the GTM tag UI
Section titled “How to wire the GTM tag UI”- Pick the Google Tag type and paste your
G-XXXXXXXtag ID into the Tag ID field so the right property receives events. - Leave Configuration settings and Shared event settings unset unless you have a shared variable that already preloads parameters; defaulting to “None” keeps things simple.
- Expand Advanced Settings and ensure the firing options stay at “Once per event” (the safest default) so tags respond predictably to each consent update.
- 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).
- Enable the built-in checks shown (
- Combine these tag-level settings with the
cookie_consent_updatedtrigger 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 (Analytics)
Section titled “PostHog (Analytics)”PostHog is currently used on the marketing site; make sure it is also behind the analytics consent:
- If PostHog is loaded via GTM, treat its tag like GA4: require
analytics_storageconsent and fire only whenconsent.analyticsis true. - If PostHog is loaded outside GTM (e.g., inline script), call
posthog.opt_out_capturing()as soon as the banner says analytics is denied andposthog.opt_in_capturing()when granted. - Use the same
cookie_consent_updatedevent to keep PostHog in sync with GTM; for example:if (!consent.analytics) {posthog.opt_out_capturing();} else {posthog.opt_in_capturing();}
factors.ai (Marketing)
Section titled “factors.ai (Marketing)”- Keep the factors.ai tag inside GTM and mark it as requiring
ad_storageconsent (marketing category). - Add a trigger that fires only when the
consent.marketingflag from thecookie_consent_updatedevent is true. - 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).
Do we need to move anything out of GTM?
Section titled “Do we need to move anything out of GTM?”You can keep GA4, PostHog, and factors.ai inside GTM as long as:
- Each tag is consent-aware (
Require Consenttoggles enabled). - Triggers depend on the
cookie_consent_updatedevent and the matching consent variable. - The banner updates
gtm.consentGranted/Deniedso 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.
Quick checklist
Section titled “Quick checklist”- Banner pushes
cookie_consent_updated+gtm.consentGranted/Deniedevery time preferences change. - GA4 tag requires
analytics_storageconsent and fires only when analytics is granted. - PostHog opt-out/in logic mirrors the analytics consent state.
- factors.ai tag requires
ad_storageconsent and respects the marketing toggle. - No tracking requests are made unless the banner’s marketing/analytics boxes are checked.