Skip to main content
Back to Guides
Setup9 min read

How to Block Scripts Before Consent

Prior blocking holds non-essential scripts until consent. CookieBeam automates this with a scanner-driven script map, a 5-layer runtime blocking engine, connection-level interception, and continuous drift detection.

The Rule That Makes This Necessary

Article 5(3) of the ePrivacy Directive says you need consent before storing or reading non-essential information on a visitor's device. "Before" is the operative word. If Google Analytics, the Meta pixel, or a chat widget runs while the banner is still on screen, the cookie is already set and consent came too late. The CJEU's Planet49 ruling (C-673/17) confirmed consent must be a prior, active choice, not something you infer after the fact.

So the technical requirement is blunt: hold every non-essential script until its category is granted. This is called prior blocking, and it's the part most broken banners get wrong. Here's how it actually works.

How CookieBeam Blocks Scripts Automatically

Most CMPs treat prior blocking as your problem. They give you a data-category attribute and tell you to tag every script by hand. That breaks the moment someone adds a tag through a plugin, a tag manager, or a third-party script that loads more scripts at runtime. You can't tag what you don't control.

CookieBeam takes a different approach. The scanner crawls your site in a headless browser, captures every script, cookie, and network connection it finds, and classifies them through a multi-layer pipeline: URL/domain pattern matching (140+ known domains), inline script content analysis (23+ vendor patterns for GTM, Meta Pixel, Hotjar, and others), an external cookie database, and WordPress plugin/theme detection (~300 known sources). You review the auto-classifications in the dashboard, adjust anything the scanner got wrong, and publish.

That publish step generates a script map baked into the CookieBeam snippet on your site. At runtime, the blocking engine uses this map to decide which scripts belong to which consent category and whether they're allowed to run. You don't tag individual scripts. The scanner already knows what's on the page.

The Five Interception Layers

A single blocking technique isn't enough. Scripts arrive on a page through multiple paths, and the engine needs to catch all of them. CookieBeam's runtime uses five layers that work together:

  1. document.createElement override. Intercepts creation of script, iframe, img, and embed elements. Proxies the src property setter and setAttribute calls. If a URL maps to an unconsented category in the script map, the element is neutralized before it can load.
  2. DOM insertion hooks. Wraps appendChild, insertBefore, and replaceChild so scripts are caught the moment they're added to the page, regardless of how they were created.
  3. MutationObserver fallback. Monitors the entire document tree for added nodes and src attribute changes as a catch-all, picking up anything the first two layers might miss.
  4. Worker blocking. Wraps Worker and SharedWorker constructors to prevent third-party worker scripts from executing without consent.
  5. Anti-tamper protection. Once a script element is neutralized (type set to text/plain, src removed), the engine locks the type property so it can't be reverted to an executable state by another script on the page.

Blocked scripts aren't deleted. They're neutralized, held in a queue, and revived in their original order when the visitor grants the relevant category.

Connection-Level Blocking

Script blocking stops code from running. But tracking doesn't always happen through script tags. A first-party script might fire a fetch() call to a tracking endpoint, send a beacon, or open a WebSocket to a third-party analytics server. Those are network connections, not script loads, and traditional prior blocking misses them entirely.

CookieBeam has a separate enforcement layer that wraps five browser APIs:

  • fetch() returns a 204 response with an X-CookieBeam-Blocked header instead of making the request.
  • XMLHttpRequest.open()/send() fires an error event without sending anything.
  • navigator.sendBeacon() returns false.
  • WebSocket constructor returns a stub that fires error and close events.
  • EventSource constructor returns a closed stub.

Each blocked connection is category-aware, so analytics connections are held while functional ones proceed. Same-site requests always pass through (first-party bypass), and you can add infrastructure host exclusions or ignore rules for specific endpoints. The engine also re-patches same-origin iframes so scripts inside them can't extract clean native API references to bypass the wrapping.

This is a separate toggle from script blocking and can be enabled independently.

Learning Mode vs. Strict Mode

When you first install CookieBeam, the blocking engine starts in learning mode. Unknown scripts (those not in the script map) are allowed to run while the scanner builds your inventory. This gives you time to review classifications and catch anything unusual before enforcement kicks in.

Once you're confident the inventory is complete, switch to strict mode. In strict mode, the engine is fail-closed: any script or connection not explicitly classified in the script map is blocked by default. An untagged marketing pixel someone dropped in through a WordPress plugin won't leak through because you hadn't scanned yet. It's held until you classify it and republish.

Start with learning mode on a new site, run a scan, review the results, then flip to strict mode for production enforcement.

Drift Detection

A scan gives you a snapshot of one moment. Sites change between scans: someone installs a plugin, a tag manager fires a new tag, a third-party script updates and pulls in a new dependency. Drift detection is the layer that catches those changes in real time.

CookieBeam's CDN script monitors for three types of drift on every page load, in every visitor's browser:

  • Cookie drift. Unknown cookies appearing that weren't in the inventory.
  • Script drift. New scripts loading that weren't in the script map.
  • Connection drift. New network connections to third parties not in the inventory.

Drift reports go to dedicated tables in the database. After five sightings, a drifted item is auto-promoted into the inventory for your review. This is continuous monitoring, not periodic scanning. It closes the gap between scans so new tracking doesn't slip through unnoticed.

Manual Tagging with data-category

The automatic engine handles most scripts without any markup changes. But if you want explicit control over a specific tag, you can still mark it yourself:

<script type="text/plain" data-category="analytics"
        src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"></script>

Setting the type to text/plain tells the browser to skip the script. The data-category attribute tells CookieBeam which consent category it belongs to. On page load, the browser sees an unknown MIME type, parses the element, and leaves it inert.

This is useful for scripts the scanner can't reach (loaded only behind authentication, injected by server-side logic you control, or added in a staging environment before the first scan). CookieBeam also accepts compatibility aliases from other CMPs, so you can migrate without re-tagging everything at once.

For most sites, you won't need to tag anything manually. The scanner and the runtime engine handle classification and enforcement. Use data-category when you need a specific override or when a script sits outside what the scanner can see.

Re-Running Scripts on Consent

Here's the catch that trips up hand-rolled solutions. You can't find a blocked tag and flip its type back to text/javascript. Once the browser has parsed a script element, changing its type attribute does nothing. The browser won't re-evaluate it.

To run a blocked script after consent, CookieBeam clones the node into a fresh element with the correct type and inserts that. The browser treats it as new and executes it. Inline scripts run immediately. External ones fire their request and execute on load. Re-injection preserves the original tag order so dependencies (a library before the code that calls it) still line up.

This applies to both automatically blocked scripts and manually tagged ones. The engine handles the clone-and-inject cycle, so you don't need to write any revival logic yourself.

The Google Tag Manager Route

If all your tags live in Google Tag Manager, you have a second option that pairs with prior blocking: gate tags with Consent Mode instead of stripping them from the page. GTM tags can be set to wait for consent signals and fire in a limited, cookieless mode until granted. This is a different mechanism (the tag loads but restrains itself) and it's how Google's own products expect to be handled. Most real sites use both: prior blocking for scripts hard-coded in the page, and Consent Mode for anything routed through GTM. Walk through the wiring in setting up Google Consent Mode with a CMP and the deeper GTM setup in Google Tag Manager and consent.

Embeds and iframes

A YouTube embed, a Google Map, or a social post is an <iframe> that sets its own cookies the moment it loads, and you can't retype an iframe the way you retype a script. The pattern here is a placeholder: swap the iframe for a lightweight "click to load" box until the relevant category is granted, then insert the real embed. This keeps marketing cookies off the page while still showing the visitor that content exists. See click-to-load consent for embeds and consent for YouTube and Vimeo embeds.

Prior-blocking checklist

  • Scanner inventory is reviewed and published

    The script map that powers the blocking engine comes from your scan results.

  • Strict mode is enabled for production

    Fail-closed: unclassified scripts are blocked until you review them.

  • Connection-level blocking is turned on

    Catches tracking via fetch, XHR, beacons, and WebSockets, not only script loads.

  • Drift detection is active

    Monitors for new scripts, cookies, and connections between scans.

  • Manually tagged scripts use data-category where needed

    For scripts the scanner can't reach or where you want explicit control.

  • Scripts re-run by cloning, not by re-typing in place

    Changing a parsed script's type doesn't re-execute it. CookieBeam handles this.

  • iframes/embeds use click-to-load placeholders

    You can't retype an iframe. Replace it until consent.

  • Network tab shows zero non-essential requests pre-consent

    The only test that proves blocking actually works.

Why Manual Tagging Alone Isn't Enough

Many third parties don't ship a simple script tag. They inject their own scripts at runtime (tag managers, ad loaders, A/B tools), so retyping the one tag you can see won't stop the ten it spawns. And tracking increasingly happens through API calls (fetch, beacons, WebSockets) that never involve a script element at all. This is why CookieBeam layers DOM interception, connection wrapping, and drift detection on top of attribute tagging: the target is everything the page loads, not only the tags you can see in the source.

How to Block Scripts Before Consent (Prior Blocking) | CookieBeam