Consent Mode for Google Tag Manager

The proper way to handle consent in Google Tag Manager without overpaying for tools or losing the data your business depends on.

Disclaimer: I’m not a lawyer and this isn’t legal advice. The approaches described here have been reviewed and deployed on reputable websites, but that doesn’t constitute a safe or compliant implementation for your situation. It’s your responsibility to validate legal requirements independently. Read the full disclaimer below.

Privacy regulations aren’t slowing down. They’re accelerating. Every year brings new state-level laws in the US, stricter enforcement from European regulators, and bigger fines for organizations that get it wrong. If you’re running Google Tag Manager on a website that serves visitors from multiple regions, consent management isn’t optional. It’s infrastructure.

The wrong approach costs real money. GDPR fines have surpassed €6 billion across roughly 2,590 cases since 2018, with an average fine of about €2.36 million. Meta alone was hit with a €1.2 billion penalty in 2023 for unlawful data transfers. On the US side, CCPA violations carry civil penalties of $2,663 to $7,988 per violation with no cap on the total, meaning a breach affecting 100,000 users could theoretically reach hundreds of millions. California’s largest CCPA settlement to date hit $1.55 million against Healthline in 2025, and the CPPA fined Honda $632,500 for making it difficult for consumers to opt out.

But fines are just one side of it. The wrong consent setup creates technical debt that compounds over time. I’ve seen organizations burn through engineering budgets building overly complex consent systems that break every time a new tag gets added, lose revenue because their analytics and ad optimization data is incomplete or inaccurate, and watch customer acquisition costs climb because conversion tracking falls apart. A broken consent implementation means your Google Ads can’t model conversions properly, your GA4 data has gaps, your remarketing audiences shrink, and your marketing team is flying blind on what’s actually working.

The goal is simple: comply with privacy laws without destroying the data you need to run your business. What follows is the architecture I’ve been using to accomplish exactly that.

The approach breaks down into three layers that work together: a free, open-source cookie consent banner that handles the UI and user preferences, a consent mode configuration that communicates those preferences to Google’s tag infrastructure, and GTM’s built-in consent controls that govern when and how every tag fires. Each layer has a clear job, and none of them overlap.

This separation is critical. Developers manage the consent code on the site. Marketers manage tags in GTM. Legal teams can review the consent categories and see exactly which tags fall under which classification. Nobody is stepping on each other’s work.

1. CookieConsent for the Privacy Banner

The consent banner is handled by vanilla-cookieconsent by Orest Bida. It’s a lightweight, open-source plugin written in plain JavaScript. MIT licensed. Zero dependencies. No signup, no subscription, no monthly fees, no page view limits.

That last point matters when you compare it to what the industry charges. OneTrust starts at roughly $827/month for their Consent & Preference Essentials plan and can climb to $2,275/month or higher for GDPR compliance modules. Enterprise customers routinely pay $10,000+ per year. Cookiebot runs $13 to $90/month depending on page count. CookieYes starts at €9/month. Even the “affordable” options add up to thousands per year across multiple domains.

CookieConsent gives you everything these paid platforms provide for consent collection: customizable banner layouts, granular category management (necessary, analytics, marketing), multi-language support, preference persistence, auto-clearing of cookies when consent is revoked, and full accessibility compliance. It follows a11y best practices out of the box. You own the code. You host it yourself. There’s no third-party dependency that could go down or change its pricing model on you.

The library defines three consent categories that map directly to Google’s consent mode parameters:

When a visitor interacts with the banner, those preferences get passed to Google’s consent API via gtag("consent", "update", {...}). That’s the bridge between the banner and tag management.

Before the banner even appears, your site needs to tell Google what the default consent state is. This happens in the <head> of your document, before the GTM container loads.

Google Consent Mode V2 introduced four parameters that control how Google tags behave:

The ad_user_data and ad_personalization parameters are the V2 additions. They were introduced to align with the EU Digital Markets Act and provide more granular control over how user data flows into Google’s advertising ecosystem.

The default configuration denies everything for EU visitors and grants everything for US visitors:

window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}

// Default: deny all for EU/EEA visitors
gtag("consent", "default", {
  ad_storage: "denied",
  analytics_storage: "denied",
  ad_user_data: "denied",
  ad_personalization: "denied",
  wait_for_update: 500
});

// Default: grant all for US visitors
gtag("consent", "default", {
  ad_storage: "granted",
  analytics_storage: "granted",
  ad_user_data: "granted",
  ad_personalization: "granted",
  region: ['US']
});

The wait_for_update: 500 parameter tells Google tags to wait 500 milliseconds for a consent update before proceeding. This gives the CookieConsent banner time to load, check for existing preferences, and push an update if the visitor has already made a choice on a previous visit.

The region array on the second block is what makes this work across jurisdictions. Without a region specified, the first block acts as the global default (deny everything). The second block overrides that default specifically for US visitors by granting all consent types. This aligns with the fundamental difference between EU and US privacy law: GDPR requires opt-in consent before any tracking, while US laws like CCPA follow an opt-out model where tracking is permitted by default with the option to decline.

This default state must be set before the GTM container script loads. If it comes after, Google tags won’t see the consent signals and will behave as if consent mode isn’t implemented at all.

When GTM loads and sees consent defaults set to denied, Google tags enter what’s called Advanced Consent Mode. The tags still load, but they don’t set cookies and don’t collect identifiable data. Instead, they send cookieless pings to Google’s servers. These pings contain no personal information but do communicate basic interaction signals that Google uses for conversion modeling.

This is the key advantage of the architecture. Even when consent is denied, Google can still model approximately 15-25% more conversions than you’d see without consent mode at all. Google’s AI analyzes patterns from visitors who did grant consent and estimates what unconsented visitors likely did. For this modeling to activate, you need at least 700 ad clicks over 7 days per country/domain pair and at least 7 full days of data collection.

When a visitor grants consent through the banner, the CookieConsent callback fires a gtag("consent", "update", {...}) call. At that point, any hits that were collected on the same page while consent was denied get automatically reprocessed with the granted status. GTM then fires a delayed pageview event, which means tags that were waiting for consent can now execute with full tracking capabilities. No data from that session is lost.

For US visitors where the default is already granted, tags fire immediately on page load with full functionality. If a US visitor later opts out through the banner, the consent update switches their state to denied and cookies get cleared.

What Actually Happens When an EU Visitor Opts In Late

This is the part that trips people up. An EU visitor lands on your site. Consent defaults to denied. The page fully renders, the visitor reads half the page, scrolls around, maybe clicks a link, and then finally hits “Accept all” on the banner 30 seconds later. What happens to all that activity? Does the pageview just vanish?

No. Here’s the sequence in detail.

While consent is denied (page load through banner interaction):

Google tags are running in Advanced Consent Mode. GA4 sends cookieless pings that contain no client ID, no user ID, and no cookies. These pings still register that a page was loaded and that basic interactions occurred, but they’re anonymous and used only for Google’s behavioral modeling. Non-Google tags (Meta Pixel, LinkedIn Insight, TikTok, etc.) don’t fire at all. They’re completely blocked by the Additional Consent Checks you configured in GTM. No data goes to those platforms during this window.

The moment consent is granted:

The onConsent callback in CookieConsent fires. Your consent handler calls gtag("consent", "update", {...}) with the granted states. Three things happen almost simultaneously:

  1. Google tags reprocess. Any cookieless pings GA4 sent on the current page get retroactively upgraded to full hits with cookies and client identifiers. Google’s consent infrastructure handles this automatically. You don’t lose the pageview, it just transitions from an anonymous ping to a fully attributed hit.

  2. GTM fires the consent_update event. This is a built-in dataLayer event that GTM pushes when consent state changes. Any tags or triggers listening for this event now execute. This is how your delayed pageview works. Tags that require ad_storage or analytics_storage and were blocked on the initial Consent Initialization and All Pages triggers now fire because their consent requirements are met.

  3. Non-Google tags fire for the first time. Meta Pixel sends its PageView event. LinkedIn Insight loads. TikTok Pixel initializes. These tags see a fully consented environment and execute normally. They capture the current page URL, referrer, and any UTM parameters that are still in the URL.

What about events that happened before consent?

This is where you need to be honest about what’s recoverable and what isn’t. For Google tags, the retroactive reprocessing covers the current page only. If the visitor navigated to a second page before granting consent, the first page’s cookieless ping stays as a cookieless ping. It feeds the modeling engine but doesn’t become a full pageview in your GA4 reports.

For non-Google tags, anything that happened before consent is gone. Meta doesn’t know the visitor was on your site. LinkedIn has no record of the visit. There’s no retroactive mechanism for these platforms because they weren’t loaded at all. This is by design. GDPR requires that no data be sent to these third parties without consent, so there’s nothing to recover.

Subsequent page loads after consent is saved:

This is where things get much cleaner. CookieConsent stores the visitor’s preferences in a cookie (by default, cc_cookie with a 182-day expiration). On the next page load, the wait_for_update: 500 window gives the CookieConsent library time to read that saved cookie and push a consent update before tags execute.

Here’s the timeline for a returning consented EU visitor:

  1. 0ms – Page starts loading. Consent defaults fire: everything denied.
  2. ~100-200ms – CookieConsent JS loads, reads the saved cc_cookie, finds that analytics and marketing were previously accepted.
  3. ~200-300ms – CookieConsent fires the onConsent callback. Your handler calls gtag("consent", "update", {...}) with all states granted.
  4. ~300-500ms – GTM processes the consent update. All tags now have their consent requirements met.
  5. 500ms – The wait_for_update window expires. Google tags that hadn’t already fired now proceed with granted consent.

The result: every tag fires with full consent, every pageview is captured with complete attribution, and every conversion pixel has the data it needs. The visitor experiences no visible delay. From a data perspective, this returning visit looks identical to a US visitor where consent was granted by default.

The edge case: visitor clears cookies or uses a new browser.

If the consent cookie is gone, the visitor is treated as a first-time visitor again. The banner reappears. Consent defaults to denied. The whole cycle repeats. This is correct behavior. GDPR requires that consent be actively given, not assumed from a previous session that the visitor may not remember.

What about single-page applications?

If your site is an SPA (React, Vue, etc.), virtual pageviews fired via dataLayer.push after a route change will respect the current consent state at the time they fire. If consent was granted on the first screen, all subsequent virtual pageviews fire normally. If consent hasn’t been granted yet, Google tags send cookieless pings for those virtual pageviews while non-Google tags remain blocked. Once consent is granted, future route changes fire with full consent. Previous virtual pageviews that were cookieless pings on Google’s side don’t get retroactively reprocessed since that only applies to the page where consent was actually granted.

Every tag you add to GTM needs a consent configuration. This is where most implementations fall apart because people add tags and forget to set the consent requirements.

Here’s the workflow:

Open any tag in GTM. Go to Advanced Settings > Consent Settings. You’ll see two options:

Built-in Consent Checks are automatic for Google’s own tags. GA4, Google Ads conversion tracking, and Google Ads remarketing tags already know which consent types they need. GA4 checks analytics_storage and ad_storage. Google Ads tags check ad_storage, ad_user_data, and ad_personalization. You don’t need to configure these manually, they’re wired in.

Additional Consent Checks are what you set for every non-Google tag. This is where you specify which consent types must be granted before the tag fires. For any advertising or marketing pixel, you’ll set ad_storage, ad_user_data, and ad_personalization as required consent. For analytics-only tools, you’ll set analytics_storage.

The rule is simple: every tag gets a consent configuration. No exceptions. Make it part of your tag creation process. When a marketer adds a new tag, consent settings are step one, not an afterthought.

Beyond the four core parameters, there are a few configuration options worth understanding:

wait_for_update: Time in milliseconds that Google tags will wait for a consent update before proceeding with the default state. Set this to 500. It gives your consent banner enough time to check for saved preferences without noticeably delaying tag execution.

region: An array of ISO 3166-2 region codes. Lets you set different defaults for different geographic regions. You can get as granular as individual US states if needed (e.g., ['US-CA'] for California specifically).

url_passthrough: When set to true, ad click information (gclid, dclid, gclsrc, wbraid, gbraid) gets passed through URLs even when ad_storage is denied. This helps preserve campaign attribution data when cookies can’t be set. Consider enabling this if you run Google Ads campaigns.

ads_data_redaction: When set to true and ad_storage is denied, ad click identifiers in network requests are redacted. This provides an additional privacy layer. Enable this if your legal team requires maximum data minimization when consent is denied.

For most implementations, the defaults plus wait_for_update and region are sufficient. Add url_passthrough if ad campaign attribution is critical to your business. Add ads_data_redaction if your legal counsel recommends it.

Every third-party tag in your GTM container needs to be classified into the correct consent category. Get this wrong and you’re either blocking tags unnecessarily (losing data) or firing them without proper consent (legal exposure).

Here’s the correct classification for the most common tags:

Analytics (analytics_storage)

Marketing / Advertising (ad_storage + ad_user_data + ad_personalization)

Marketing / Advertising (ad_storage + ad_user_data)

A few notes on these classifications. GA4 has built-in consent checks for analytics_storage and ad_storage, so its consent handling is mostly automatic. However, when GA4 data is linked to Google Ads for audience sharing or conversion imports, the ad_user_data and ad_personalization signals also apply.

Meta, TikTok, Snapchat, Reddit, X, and LinkedIn pixels do not natively support Google Consent Mode. They don’t read consent signals from the gtag API. For these tags, you rely on GTM’s Additional Consent Checks to block them from firing when consent isn’t granted. When consent is denied, no data goes to these platforms at all. There’s no cookieless ping equivalent like Google offers.

HubSpot, Zoho, and ZoomInfo are marketing tools that collect user data for lead identification and CRM enrichment. They should require ad_storage and ad_user_data at minimum since they’re processing visitor data for business purposes. If any of these tools are used for ad targeting or remarketing, add ad_personalization as well.

The important principle: when in doubt, classify a tag as marketing. It’s better to require consent and potentially lose some data than to fire a tracking pixel without consent and face regulatory action.

Example Implementation

The following code examples show a complete consent mode implementation using CookieConsent and GTM. This is adapted from a production setup and can be modified for any site running GTM.

This goes in your document <head>, before any other scripts:

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",
  wait_for_update: 500
});

gtag("consent", "default", {
  ad_storage: "granted",
  analytics_storage: "granted",
  ad_user_data: "granted",
  ad_personalization: "granted",
  region: ['US']
});

Then immediately after, load your GTM container:

(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');

Replace GTM-XXXXXXX with your actual container ID.

This class initializes the CookieConsent banner and handles the bridge between user preferences and Google’s consent API. Include the CookieConsent CSS and JS from the CDN, then initialize this class when your app loads.

Dependencies:

// CSS
// https://cdn.jsdelivr.net/npm/[email protected]/dist/cookieconsent.css

// JS
// https://cdn.jsdelivr.net/npm/[email protected]/dist/cookieconsent.umd.js

The consent handler:

/**
 * ConsentGTM
 *
 * Handles GTM consent initialization, Global Privacy Control
 * detection, and CookieConsent banner callbacks.
 */
class ConsentGTM {
  constructor() {
    this.gtm();
    this.cookieConsent();
  }

  gtm() {
    window.dataLayer = window.dataLayer || [];
    window.gtag = function () {
      window.dataLayer.push(arguments);
    };

    if (navigator.globalPrivacyControl === true) {
      gtag("consent", "update", {
        ad_storage: "denied",
        ad_user_data: "denied",
        ad_personalization: "denied",
      });
    }
  }

  consent({ cookie }) {
    const analyticsGranted = cookie.categories.includes("analytics");
    const marketingGranted = cookie.categories.includes("marketing");

    gtag("consent", "update", {
      analytics_storage: analyticsGranted ? "granted" : "denied",
      ad_storage: marketingGranted ? "granted" : "denied",
      ad_user_data: marketingGranted ? "granted" : "denied",
      ad_personalization: marketingGranted ? "granted" : "denied",
    });
  }

  cookieConsent() {
    if (!window.CookieConsent) return;
    window.CookieConsent.run({
      categories: {
        necessary: { enabled: true, readOnly: true },
        analytics: {},
        marketing: {},
      },
      language: {
        default: "en",
        translations: {
          en: {
            consentModal: {
              title: "We use cookies",
              description:
                "This website uses cookies to ensure basic functionality, enhance user experience, and provide personalized content and ads. You can manage your preferences or withdraw consent at any time.",
              acceptAllBtn: "Accept all",
              acceptNecessaryBtn: "Reject non-essentials",
              showPreferencesBtn: "Manage Individual preferences",
            },
            preferencesModal: {
              title: "Manage cookie preferences",
              acceptAllBtn: "Accept all",
              acceptNecessaryBtn: "Reject non-essentials",
              savePreferencesBtn: "Save preferences",
              closeIconLabel: "Close modal",
              sections: [
                {
                  title: "Strictly Necessary cookies",
                  description:
                    "These cookies are essential for our website to function properly and cannot be disabled.",
                  linkedCategory: "necessary",
                },
                {
                  title: "Performance and Analytics",
                  description:
                    "These cookies are essential for monitoring our website's health and identifying technical problems.",
                  linkedCategory: "analytics",
                },
                {
                  title: "Marketing & Advertising",
                  description:
                    "We use these cookies to tailor our advertising to your interests and measure campaign success.",
                  linkedCategory: "marketing",
                },
              ],
            },
          },
        },
      },
      onConsent: (data) => this.consent(data),
      onChange: (data) => this.consent(data),
    });
  }
}

WordPress Integration

If you’re running WordPress, enqueue the CookieConsent assets and initialize the class in your theme:

/**
 * Enqueue Consent Scripts
 *
 * Loads CookieConsent library and custom consent handler
 * for GTM integration.
 */
function enqueue_consent_scripts() {
  wp_enqueue_style(
    'cookieconsent',
    'https://cdn.jsdelivr.net/npm/[email protected]/dist/cookieconsent.css',
    [],
    '3.1.0'
  );

  wp_enqueue_script(
    'cookieconsent',
    'https://cdn.jsdelivr.net/npm/[email protected]/dist/cookieconsent.umd.js',
    [],
    '3.1.0',
    true
  );

  wp_enqueue_script(
    'consent-gtm',
    get_template_directory_uri() . '/js/consent-gtm.js',
    ['cookieconsent'],
    '1.0.0',
    true
  );
}
add_action('wp_enqueue_scripts', 'enqueue_consent_scripts');

Then in your consent-gtm.js file, instantiate the class:

document.addEventListener("DOMContentLoaded", function () {
  new ConsentGTM();
});

Global Privacy Control

Notice the navigator.globalPrivacyControl check in the gtm() method. GPC is a browser-level signal that tells websites the visitor has opted out of data selling and sharing. California’s CCPA requires businesses to honor GPC signals. When detected, the implementation immediately updates consent to deny all advertising-related storage, regardless of region defaults. This means even US visitors with GPC enabled will have their ad-related consent denied automatically.

Full Disclaimer

I am not a lawyer, a legal professional, or a certified privacy consultant. Nothing in this article constitutes legal advice, and it should not be treated as such.

The consent mode architecture and code examples described here have been reviewed, tested, and deployed on production websites. These implementations have been used on reputable sites serving real traffic across multiple jurisdictions. However, the fact that an approach works on one website does not mean it will satisfy the legal requirements for yours.

Privacy regulations vary significantly by jurisdiction, industry, and the specific nature of your data processing activities. GDPR, CCPA/CPRA, ePrivacy Directive, the Digital Markets Act, and the growing list of US state privacy laws each have distinct requirements around consent collection, data storage, user rights, and enforcement mechanisms. What constitutes compliant consent in one jurisdiction may be insufficient in another.

Additionally, these laws change. Enforcement interpretations evolve. New regulations emerge. What is considered adequate today may not be tomorrow.

Before implementing any consent management system, you should:

The author of this article, the website publishing it, and any parties referenced in it accept no liability for any legal consequences, fines, penalties, or damages that may result from implementing the approaches described. You are solely responsible for ensuring your implementation meets all applicable legal requirements.

When in doubt, ask a lawyer. The cost of a legal consultation is a rounding error compared to the cost of a privacy violation.

References