ConsentStackDocs

Script Blocking

ConsentStack automatically detects and blocks third-party scripts until your visitors give consent.

ConsentStack finds the third-party scripts running on your site and blocks them until your visitors consent to the relevant category. No manual tagging required for known scripts. It just works.

How scripts are detected and categorized

When a visitor loads your page, ConsentStack checks every third-party script in the browser, as it loads, against a built-in database of 900+ known tracker patterns from 900+ vendors. If a script belongs to a recognized vendor (Google Analytics, Meta Pixel, HubSpot, and hundreds more) it is automatically assigned to the right consent category.

Scripts from domains that are not in the database are flagged as unassigned in your dashboard so you can review and categorize them yourself.

What happens when you add a new tracker?

It is checked the first time it loads. ConsentStack does not crawl your site on a schedule and does not carry a list of trackers from a last scan. The check runs in the visitor's browser on every page load, against the 900+ tracker patterns built into the script itself, so there is no rescan to wait for and nothing to republish. A scan-based consent tool works the other way round: it re-crawls your site on a schedule (commonly monthly, with daily scans sold as an add-on) and can only block what its last crawl found, so a tag added between two crawls fires before consent until the next one.

Say your marketing team adds a new pixel through Google Tag Manager on a Tuesday afternoon. On the very next page load, whether that visitor is the first or the millionth:

  • If the tracker is in the catalog, it is blocked before it executes, on that first load, until the visitor consents to its category. This covers tags injected at runtime by a tag manager, an A/B testing tool or a chat widget, not only script tags written into your HTML, and it covers the network calls the tracker would make (fetch, XMLHttpRequest, sendBeacon, pixels, iframes), as described further down this page.
  • If the tracker is not in the catalog, it is recorded but not blocked. It appears in your dashboard's tracker inventory as Unassigned (uncategorized) the first time any visitor loads it, with the date it was first seen, the date it was last seen and the pages it appeared on. Assigning it a category, or blocking it outright, is one click. Your configuration regenerates automatically and the tracker is blocked before consent on every page load from then on.

That second case is the honest boundary, stated in the terms people use when they compare tools:

ApproachWhen a new tracker is noticedWhat happens before it is categorized
Scan-based (crawl on a schedule, block what the crawl found)On the next scheduled crawlIt runs. The gap is everything added since the last scan.
Runtime catalog (ConsentStack)On its first page loadA catalog tracker is blocked before execution. An unknown one is recorded as Unassigned and runs until you categorize it.
Default-deny (block every unknown request)On its first page loadIt is blocked, and so is any legitimate script the tool has not seen, until someone allows it.

ConsentStack is a known-tracker blocklist evaluated at runtime, not a default-deny firewall: a script from a domain the catalog has never seen runs until you categorize it. That default means a new script cannot silently break your checkout, chat or embeds before a person has looked at it. The trade is that the gap for a scan-based tool is "anything added since the last scan", while the gap here is "anything outside a 900+-vendor catalog", and you see the second gap in your dashboard on the first page load that carries it, not weeks later.

Two things help when you want to be strict about it:

  • Test mode runs the same checks in report-only mode, recording what would have been blocked without blocking anything, so you can review the inventory before you enforce. See testing on a live site without showing the banner.
  • Load order. ConsentStack has to be the first script in your <head>. A tracker that loads before it can fire before the check exists, whichever consent tool you use. See SDK installation.

To see the behaviour for yourself, add a throwaway tag through your tag manager, open the Network panel in a fresh private window, and confirm that nothing leaves the browser before you click anything on the banner. The steps are in how to verify any consent tool actually blocks before consent.

Two ways to categorize scripts

MethodHow it worksWhen to use it
AutomaticConsentStack recognizes the script's domain and assigns a category based on its known vendor database.Works out of the box for the vast majority of third-party scripts. No action needed.
Manual rulesYou create a custom rule in the dashboard for a specific domain, assigning it to any category you choose.For proprietary or niche scripts that the vendor database does not cover.

Manual rules always take priority. If you create a custom rule for a domain that is already auto-categorized, your rule wins.

Pattern syntax for manual rules

A manual rule's pattern is matched against the hostname of each script request. Two forms are supported:

FormExampleMatchesDoes not match
Plain domainzi-scripts.comzi-scripts.com, js.zi-scripts.comzi-scripts.com.evil.net
Wildcard (*)s3-*.amazonaws.coms3-us-west-2.amazonaws.combucket.s3-us-west-2.amazonaws.com

Rules of thumb:

  • A plain domain covers itself and every subdomain. This is the right form for almost all rules.
  • * matches any run of characters, including dots. Wildcard patterns must describe the whole hostname: s3-*.amazonaws.com does not cover bucket.s3-us-west-2.amazonaws.com. Add a *. variant (*.s3-*.amazonaws.com) when you need subdomain coverage too.
  • When several rules match the same hostname, the longest pattern wins.
  • Rules assigned to the essential category never block anything; essential is always allowed.
  • A pattern of just * matches every third-party hostname on your site. That gates everything under one category, which is almost never what you want.

What blocking looks like in practice

Scripts are stopped before they execute, not cleaned up after the fact. This is an important distinction: no tracking code runs until your visitor has actively given consent for that category.

When a visitor gives consent:

  • Blocked scripts activate immediately. There is no page reload required. Scripts in the consented category start running right away, in their original page order. If a tracker has multiple scripts (for example, a config script and a pageview script), they execute in the correct sequence: the config script loads and finishes before the pageview script starts.

When a visitor revokes consent:

  • Already-executed scripts cannot be un-run. A script that has already loaded and sent data cannot be retroactively stopped. ConsentStack shows the visitor a gentle reminder to refresh the page, which clears the scripts from memory and re-applies blocking.

The key trust signal: scripts are blocked before execution, not cleaned up afterward. Your visitors' data is never sent to a third party without their consent.

It describes three different mechanisms, and they do not protect visitors equally. A consent tool can rewrite script tags so the browser skips them, intercept the network calls a tracker makes, or let everything load and then tell the vendor what the visitor chose. Only the first two stop data leaving the browser.

MechanismWhat it doesWhat still gets through
Tag rewritingRewrites <script> elements so the browser will not execute them until consent is given.Anything that is not a script tag: fetch() and XMLHttpRequest calls, navigator.sendBeacon, tracking pixels loaded as images, iframes.
Request interceptionWraps the browser APIs a tracker uses to reach the network, so a blocked call never leaves the page at all.Tags written directly into the initial HTML, which the browser's preload scanner fetches before any JavaScript runs (see the caveat below).
Consent signallingLets tags load and run, then tells the vendor what the visitor chose. Google Consent Mode is the common example.Everything. The request reaches the third party either way. The vendor decides what to do with it.

Consent signalling is worth having, and ConsentStack sends those signals too. But it is not blocking, and the two get described with the same words. Under GDPR the connection itself is the disclosure: once a request reaches an ad network, that network has your visitor's IP address and can set cookies on its own domain, whatever the payload said about consent.

This is why "blocks scripts" and "blocks requests" are different claims. A tool that only rewrites <script> tags will still let a fetch() call to an ad endpoint through, because no script tag was ever involved.

What ConsentStack intercepts

ConsentStack wraps the network-reaching browser APIs, not just script tags:

SurfaceCovered
<script>, <iframe>, <img>Yes, intercepted when the src is set
fetch(), XMLHttpRequestYes
navigator.sendBeacon()Yes
WebSocket, EventSourceYes
document.cookie, localStorage / sessionStorageYes, writes are gated by category

Blocked calls fail at the wrapper. Nothing is sent and no connection is opened, so there is no request for a third party to log.

For developers

If you need more control, you can manually tag any script with a data-cs-category attribute to assign it to a specific consent category. This is useful for inline scripts or cases where you want to override automatic detection.

For implementation details, see SDK installation.

The preload-scanner caveat

ConsentStack intercepts third-party scripts at the moment their src is set in JavaScript. This catches every standard tracker bootstrap pattern (Google Tag Manager, HubSpot, Microsoft Clarity, Meta Pixel, and so on) when they are injected via JS. It does not catch tags written directly into the initial HTML such as <script async src="..."> or <link rel="preload" as="script">. The browser's preload scanner walks the markup ahead of the parser and dispatches fetches for those URLs before any JavaScript runs. The third party receives the request and may set cookies on its own domain even though the script's execution is later blocked.

Two situations make this matter:

  1. Plain HTML / Webflow / WordPress where you paste a vendor's <script async src="..."> snippet straight into the head.
  2. SSR'd Next.js layouts where <Script> and @next/third-parties (<GoogleTagManager>, <GoogleAnalytics>) emit <link rel="preload" as="script"> into the rendered HTML.

Solution: opt-in markup with application/cs-resolve

For tags written directly in HTML, mark them with a non-executable type so the preload scanner ignores them. ConsentStack walks these tags after consent is determined and rewrites them to the executable form for any category the visitor has granted.

<!-- External script: parser will not fetch -->
<script type="application/cs-resolve" async
        src="https://www.googletagmanager.com/gtag/js?id=AW-XXXXX"></script>

<!-- Inline script: requires data-cs-category since there is no src to look up -->
<script type="application/cs-resolve" data-cs-category="analytics">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date()); gtag('config', 'AW-XXXXX');
</script>

<!-- Stylesheet: parser will not fetch until rel is rewritten -->
<link rel="cs-stylesheet" href="https://example.com/widget.css">

How classification works:

  1. If data-cs-category is set, that category is used (always wins).
  2. Otherwise, the tag's src (or href) domain is matched against the catalog. Hundreds of common vendors are auto-classified.
  3. If neither resolves a category, the tag stays dormant.

Inline scripts have no src to classify, so they require an explicit data-cs-category. External scripts and stylesheets from known vendors do not.

Many widget libraries (Calendly, Intercom, HubSpot, Drift) inject their own styles when their script initializes, so a separate <link> for the stylesheet is often redundant. Try removing the <link> first; only mark it with rel="cs-stylesheet" if the widget visibly breaks without it.

Solution: SSR-emitted preloads in Next.js

For frameworks that auto-emit preloads, two patterns avoid the leak:

  • Inline the bootstrap snippet with dangerouslySetInnerHTML. The inline <script> has no src, so no preload is emitted, and the runtime injection it performs is intercepted normally. This is the simplest pattern for layout-level trackers. See Framework Guides for the full pattern.
  • Gate <Script> on a consent hook. Wrap it in a client component that returns null until useConsentValue reports the relevant category as granted. The SSR pass renders null, so no preload appears in the initial HTML.

What's next