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.
Approach 2: Tag Manager Integration (GTM Consent Mode)
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:
- CMP SDK loads early in
<head>(must be small enough to load before other scripts) - MutationObserver is installed, watching for all DOM mutations
- As the browser encounters
<script>elements, the observer fires before the browser fetches thesrcURL - For matched scripts, the observer changes the
typeattribute totext/blocked, preventing execution - Original script source is stored for re-injection after consent
- 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:
- Parser creates the DOM node
- Parser inserts it into the DOM tree
- MutationObserver callback fires (synchronously)
- Browser evaluates the script's
typeattribute - If the type is recognized JavaScript, the browser fetches and executes
- If the type is unrecognized, the browser skips execution
The critical window is between steps 2 and 4:
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:
- Explicit `data-cs-category` attribute on the script tag (manual classification takes precedence)
- Domain match against the tracker database
- 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.
<!-- 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>Re-Enabling Scripts After Consent
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
| Factor | Post-Load / Retrofit | GTM Consent Mode | Parse-Time MutationObserver |
|---|---|---|---|
| When it acts | After page load (100-500ms delay) | Before tag execution (if defaults set) | During HTML parsing (before script fetch) |
| Scripts caught | Only scripts in DOM at scan time | Only GTM-managed tags | All scripts added to DOM |
| Cookies prevented | No | Partially (GTM tags only) | Yes |
| Data leakage window | 100-500ms+ | None for GTM tags; full exposure for others | None |
| Handles direct script tags | Retroactively only | No | Yes |
| Handles dynamic injection | Sometimes | No (unless via GTM) | Yes |
| Handles iframes | Retroactively only | No | Yes |
| SDK size requirement | None | None (relies on GTM) | Must be small (<20KB) |
| Compliance level | Incomplete | Partial | Complete for observed elements |
Frequently Asked Questions
Cookie consent script blocking prevents tracking scripts from executing until the user grants consent through a banner. Without it, the banner is purely cosmetic: it asks for permission while tracking scripts run independently.
Most CMPs use post-load blocking, arriving 100-500ms after tracking scripts have already executed. Many sites also rely solely on GTM consent mode, which only controls GTM-managed scripts. Direct script tags and CMS-loaded scripts bypass GTM entirely.
MutationObserver blocking operates at the DOM level, catching any script element regardless of how it got there. GTM consent mode only controls scripts deployed through Google Tag Manager. The two are complementary: parse-time blocking catches everything, while GCM v2 provides protocol-level consent signaling to Google tags.
A well-implemented blocker should not break essential functionality. The tracker database only contains known tracking domains. Manual tagging with `data-cs-category="essential"` provides explicit override control. Testing is critical: verify core functionality (payment flows, authentication, navigation) in a denied-consent state.
They remain in the DOM as inert elements with `type="text/blocked"`. They don't execute, don't download, don't set cookies, and don't send network requests. No performance impact.
Open DevTools before loading the page. In the Network tab (filter by JS), no requests to tracker domains should appear before interacting with the banner. In the Application tab, no tracking cookies (`_ga`, `_gid`, `_fbp`, `_hjid`) should be set before consent.
They work together. For Google tags, ConsentStack allows the scripts to load (because GCM v2 signals control their behavior at the protocol level) while blocking non-Google tracking scripts at the DOM level. Protocol-level control for Google, enforcement-level control for everything else. ---
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.
Try ConsentStack free. No credit card. No sales call. Parse-time script blocking that actually works, live in under 10 minutes.