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+ in CMPs like Cookiebot or OneTrust), 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 rewrites the type attribute to text/plain and stamps data-cs-blocked="true", which the browser refuses to execute
  5. The blocked element stays in the DOM, ready to be cloned and re-injected once consent is granted
  6. Approved scripts are re-injected when consent is granted; denied scripts remain blocked

This is the approach ConsentStack uses. The observer is installed on document.documentElement at script parse time before DOM ready, so it sees every <script> element the parser inserts. The type rewrite happens before the browser hands the element to the script-execution pipeline. A separate pre-parse stub goes further: it patches DOM descriptors and network APIs to block tracker requests before the core SDK even loads.

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.type = 'text/plain';
    scriptElement.setAttribute('data-cs-blocked', 'true');
    scriptElement.setAttribute(
      'data-cs-category-resolved',
      classifiedCategory
    );
    // Element stays in the DOM. Browser refuses to execute
    // anything whose type is not a recognized JavaScript MIME.
  }
}

Setting type to text/plain prevents execution because the browser only runs elements whose type matches a recognized JavaScript MIME. The element stays in the DOM, marked with data-cs-blocked="true", so the SDK can find it again at consent time.

Domain Matching: 900+ Tracker Patterns

ConsentStack ships with a curated library of 900+ tracker patterns sourced from open tracker databases. Each pattern is mapped to a consent category. Classification follows a three-tier priority order:

  1. Explicit data-cs-category attribute on the script tag (manual classification takes precedence)
  2. Server-provided script rules for the site (custom domain rules) and the auto-generated rules from the 900+ tracker pattern library
  3. Default: allow. Unknown scripts execute normally to avoid over-blocking; the SDK still records them via beacon so the dashboard can flag new trackers

Why the SDK Must Be Small

Parse-time blocking is only worth doing if the blocker arrives before the trackers do. A 150KB+ CMP takes hundreds of milliseconds to download. ConsentStack splits its weight between a tiny pre-parse stub that loads synchronously and patches DOM and network APIs immediately, and a deferred core SDK with the dashboard, banner, and reporting logic. Total weight: ~30 KB gzipped (stub + core). The size discipline is architectural: a parse-time blocker that ships 150KB+ is a contradiction in terms.

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. Flipping type back to text/javascript on the original element does nothing: browsers cache the execution decision at element creation time. The SDK clones the blocked node into a fresh <script> element (without the blocking attributes), inserts it where the original lived, and chains external scripts together via onload so they fire in their original order. Revocation is handled separately by reloading the page, because executed scripts cannot be undone.

External scripts are released sequentially, with each clone's onload kicking off the next. That preserves the original execution order (analytics tags often depend on each other) and avoids the post-consent avalanche where 15-20 third-party scripts kick off at once and freeze the main thread.

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 ~30 KB stub-plus-core architecture instead of a 150KB+ compliance platform like OneTrust.

ConsentStack was built on this foundation. Parse-time blocking, 900+ auto-classified tracker patterns, and a ~30 KB stub-plus-core 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.