Blog

How Cookie Consent Script Blocking Actually Works (And Why Most CMPs Fake It)

Key Takeaways

  • Essential scripts: Application framework, authentication, payment processing, security. Always allowed.
  • Analytics scripts: Google Analytics, Hotjar, Mixpanel. Require consent under GDPR.
  • Marketing scripts: Meta Pixel, Google Ads, TikTok Pixel, LinkedIn Insight Tag. Require explicit consent.
  • Functional scripts: Live chat, embedded videos, social embeds. Often set their own cookies.

What Script Blocking Means (And Why It's the Whole Point)

Privacy regulations don't care about cookie banners. They care about consent. Without script blocking, a consent banner is decorative. It asks a question but doesn't wait for the answer. By the time the user reads the banner text, the cookies are already set, the pixels have fired, and the data has left the browser.

What Needs to Be Blocked

Not every script requires consent. The blocking logic must distinguish between:

  • Essential scripts: Application framework, authentication, payment processing, security. Always allowed.
  • Analytics scripts: Google Analytics, Hotjar, Mixpanel. Require consent under GDPR.
  • Marketing scripts: Meta Pixel, Google Ads, TikTok Pixel, LinkedIn Insight Tag. Require explicit consent.
  • Functional scripts: Live chat, embedded videos, social embeds. Often set their own cookies.

The challenge isn't the logic. It's the timing.

Learn about consent categories

The Three Approaches to Script Blocking

Approach 1: Post-Load / Retrofit Blocking

The CMP loads after the page, scans the DOM for known tracking scripts, and attempts to disable them retroactively.

How it works: Browser parses HTML. Third-party scripts load and execute. Cookies are set, pixels fire. CMP script loads (often 150KB+), initializes, scans DOM, renders banner.

The fundamental problem: By the time the CMP arrives, cookies are in the browser and data has been sent to remote servers. Removing a script element from the DOM cannot un-send a network request or un-set an HTTP-only cookie.

Tracking scripts typically finish executing before a large CMP finishes downloading and rendering its banner. That gap is an entire window of unblocked tracking.

Who uses this: Many budget CMPs and WordPress consent plugins.

The CMP communicates consent state to GTM, which holds tags in a pending state until consent signals are received.

What this gets right: For scripts deployed through GTM, the timing is correct. Google tags respect consent signals natively.

The gap: Not all scripts go through GTM. Direct <script> tags in your HTML, CMS plugins, embedded iframes, and dynamically injected code all bypass GTM entirely.

Termly's documentation makes this explicit: their Auto Blocker "does not work with scripts deployed via Google Tag Manager." But GTM is the most common deployment method. A script blocker that doesn't work with GTM, and a GTM integration that doesn't work with direct scripts, leaves a gap no matter which you choose.

Read the Google Consent Mode v2 setup guide

Approach 3: Parse-Time Blocking via MutationObserver

The third approach intercepts scripts at the DOM level before the browser can fetch or execute them, using the browser's MutationObserver API.

How it works:

  1. CMP SDK loads early in <head> (must be small enough to load before other scripts)
  2. MutationObserver is installed, watching for all DOM mutations
  3. As the browser encounters <script> elements, the observer fires before the browser fetches the src URL
  4. For matched scripts, the observer changes the type attribute to text/blocked, preventing execution
  5. Original script source is stored for re-injection after consent
  6. Approved scripts are re-injected when consent is granted; denied scripts remain blocked

This is the approach ConsentStack uses. MutationObserver callbacks fire synchronously during DOM parsing. The browser creates the DOM node first, then begins fetching. The observer intercepts at creation time, before the fetch begins.

Who uses this: ConsentStack and Transcend (at enterprise pricing, averaging $130K/year).

How Parse-Time MutationObserver Blocking Works

Intercepting Scripts Before Execution

When the browser's HTML parser encounters a <script> element:

  1. Parser creates the DOM node
  2. Parser inserts it into the DOM tree
  3. MutationObserver callback fires (synchronously)
  4. Browser evaluates the script's type attribute
  5. If the type is recognized JavaScript, the browser fetches and executes
  6. If the type is unrecognized, the browser skips execution

The critical window is between steps 2 and 4:

javascript
function handleScript(scriptElement) {
  const src = scriptElement.src;

  if (shouldBlock(src, scriptElement)) {
    scriptElement.setAttribute('data-original-type',
      scriptElement.type || 'text/javascript'
    );
    scriptElement.type = 'text/blocked';

    if (src) {
      scriptElement.setAttribute('data-original-src', src);
      scriptElement.removeAttribute('src');
    }
  }
}

Setting type = 'text/blocked' prevents execution. Removing src ensures no network request is made.

Domain Matching: The 6,592 Tracker Database

ConsentStack uses a curated list of 6,592 tracker domains from DuckDuckGo's Tracker Radar. Each domain is pre-classified into a consent category. The matching follows a priority order:

  1. Explicit `data-cs-category` attribute on the script tag (manual classification takes precedence)
  2. Domain match against the tracker database
  3. Default: allow. Unknown scripts execute normally, preventing over-blocking

Why the SDK Must Be Small

The MutationObserver must be installed before other scripts are parsed. A 150KB+ CMP takes hundreds of milliseconds to download. ConsentStack's SDK is under 10KB gzipped, downloading in under 20ms on 4G and initializing in under 5ms. The size constraint is architectural, not cosmetic. A parse-time blocker that ships 150KB+ is a contradiction.

html
<!-- Loads and installs MutationObserver before other scripts execute -->
<script src="https://cdn.consentstack.io/sdk/v1/cs.js"
        data-cs-site="your-site-id"></script>

Get started with ConsentStack

When consent is granted, blocked scripts must execute as if never blocked. You cannot simply change type back to text/javascript on the blocked element. Browsers cache the execution decision at element creation time. You must create a new <script> element, copy attributes and content, and insert it into the DOM.

Scripts are released using requestAnimationFrame-based staggering to prevent main thread saturation. Releasing 15-20 scripts simultaneously causes a "post-consent avalanche" that can freeze the page for 500ms.

Learn more about consent performance

Edge Cases

Inline scripts lack a src URL for domain matching. Manual classification via data-cs-category is the most reliable approach. Alternatively, blocking the companion external script (e.g., fbevents.js) causes inline fbq() calls to fail silently.

Dynamic script injection (scripts created via document.createElement) triggers DOM mutations and is handled identically to parser-inserted scripts.

Iframes are the hardest case. Cross-origin iframes are sandboxed by the browser's same-origin policy. The solution is blocking the <iframe> element itself, replacing src with about:blank until consent is granted.

Preloaded resources (<link rel="preload">) and API-based tracking (navigator.sendBeacon, fetch()) require additional handling: intercepting preload link elements and wrapping browser APIs to check request URLs before allowing calls.

Comparison: Script Blocking Methods

FactorPost-Load / RetrofitGTM Consent ModeParse-Time MutationObserver
When it actsAfter page load (100-500ms delay)Before tag execution (if defaults set)During HTML parsing (before script fetch)
Scripts caughtOnly scripts in DOM at scan timeOnly GTM-managed tagsAll scripts added to DOM
Cookies preventedNoPartially (GTM tags only)Yes
Data leakage window100-500ms+None for GTM tags; full exposure for othersNone
Handles direct script tagsRetroactively onlyNoYes
Handles dynamic injectionSometimesNo (unless via GTM)Yes
Handles iframesRetroactively onlyNoYes
SDK size requirementNoneNone (relies on GTM)Must be small (<20KB)
Compliance levelIncompletePartialComplete for observed elements

Frequently Asked Questions

Conclusion

Script blocking is the difference between consent theater and consent enforcement.

Post-load blocking acts too late. Cookies are set and data is transmitted before the CMP loads.

GTM consent mode acts at the right time, but only for GTM-managed scripts. Partial coverage is not compliance.

Parse-time MutationObserver blocking acts at the right time and catches everything added to the DOM. It requires a small, fast SDK, which is an architectural constraint that produces a better product: a sub-10KB SDK instead of a 150KB+ compliance platform.

ConsentStack was built on this foundation. Parse-time blocking, 6,592 auto-classified tracker domains, and a sub-10KB SDK. The consent banner isn't the product. The enforcement is.

If your current CMP shows a banner while cookies are being set behind it, check the Network tab. Check the cookies. The data doesn't lie.

Start free with ConsentStack

Try ConsentStack free. No credit card. No sales call. Parse-time script blocking that actually works, live in under 10 minutes.