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 scans for third-party scripts and checks each one 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.
Two ways to categorize scripts
| Method | How it works | When to use it |
|---|---|---|
| Automatic | ConsentStack 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 rules | You 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:
| Form | Example | Matches | Does not match |
|---|---|---|---|
| Plain domain | zi-scripts.com | zi-scripts.com, js.zi-scripts.com | zi-scripts.com.evil.net |
Wildcard (*) | s3-*.amazonaws.com | s3-us-west-2.amazonaws.com | bucket.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.comdoes not coverbucket.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.
What does "blocks scripts before consent" actually mean?
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.
| Mechanism | What it does | What still gets through |
|---|---|---|
| Tag rewriting | Rewrites <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 interception | Wraps 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 signalling | Lets 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:
| Surface | Covered |
|---|---|
<script>, <iframe>, <img> | Yes, intercepted when the src is set |
fetch(), XMLHttpRequest | Yes |
navigator.sendBeacon() | Yes |
WebSocket, EventSource | Yes |
document.cookie, localStorage / sessionStorage | Yes, 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:
- Plain HTML / Webflow / WordPress where you paste a vendor's
<script async src="...">snippet straight into the head. - 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:
- If
data-cs-categoryis set, that category is used (always wins). - Otherwise, the tag's
src(orhref) domain is matched against the catalog. Hundreds of common vendors are auto-classified. - 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 nosrc, 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 returnsnulluntiluseConsentValuereports the relevant category as granted. The SSR pass rendersnull, so no preload appears in the initial HTML.
What's next
- Check the difference yourself: how to verify any consent tool actually blocks before consent
- Learn about consent models and how ConsentStack decides what your visitors see
- See how to review and manage detected scripts in the dashboard