Blog

How Cookie Consent Script Blocking Works (And What Every CMP's Blocking Attribute Does)

Script blocking is how a consent banner stops tracking code from running until a visitor agrees to it. Mechanically it is simpler than it sounds. The platform takes a script tag the browser would otherwise execute, neutralizes it, records which consent category it belongs to, and puts it back the moment that category is granted.

Key Takeaways

  • 01Blocking works by neutralizing a tag before the browser runs it, usually by setting type to text/plain, then restoring it once the visitor consents.
  • 02Every platform uses its own category attribute. Cookiebot uses data-cookieconsent, OneTrust uses optanon-category classes, Termly uses data-categories, CookieScript and Pandectes use data-cookiecategory.
  • 03Iframes and images have no type attribute, so they are blocked differently. Cookiebot swaps src for data-cookieblock-src.
  • 04Manual markup only blocks the tags you remember to mark. Anything added later by a tag manager, a plugin or another script goes straight through.
  • 05Across 229 sites we scanned, 78 to 83 percent sent a third-party request before the visitor chose anything.

The neutralizing step is nearly always the same trick: set the tag's type to something the browser does not recognize as JavaScript, usually text/plain, and the browser treats the whole thing as inert text. What differs between platforms is the attribute that carries the category, and that is the part people actually get stuck on.

This page covers both: the mechanism, and the exact attribute each major consent platform expects.

What blocking actually does to a tag

A browser decides whether to run a script by looking at its type attribute. Give it a value it does not understand and the browser skips execution entirely, leaving the tag sitting in the DOM as text. Consent platforms use that as an off switch, then flip it back on after consent by restoring the real type and re-inserting the tag.

Before and after blocking
<!-- Normal: the browser runs this immediately -->
<script src="https://cdn.example-tracker.com/pixel.js"></script>

<!-- Blocked: inert until the visitor consents -->
<script type="text/plain"
        data-cookieconsent="marketing"
        src="https://cdn.example-tracker.com/pixel.js"></script>

The important part is timing. This only works if the tag is neutralized before the browser reaches it. If a platform gets there late, the script has already run, the request has already gone out, and no amount of blocking afterwards undoes it. That single detail is the difference between a banner that works and a banner that is decorative.

The blocking attribute for every major CMP

The mechanism is shared, but the vocabulary is not. If you are moving between platforms, or debugging a site someone else set up, this is the translation table.

Manual script blocking syntax by consent platform
PlatformNeutralized withCategory attributeExample values
Cookiebottype="text/plain"data-cookieconsentpreferences, statistics, marketing
OneTrusttype="text/plain"class="optanon-category-C000X"C0001 necessary, C0002 performance, C0003 functional, C0004 targeting, C0005 social
Termlytype="text/plain"data-categoriesessential, performance, analytics, advertising, social networking
CookieScripttype="text/plain" plus data-cookiescript="accepted"data-cookiecategorystrict, functionality, performance, targeting
Pandectestype="javascript/blocked"data-cookiecategoryfunctionality, performance, targeting
CookieYestype="text/plain"data-cookieyescookieyes-functional, cookieyes-analytics, cookieyes-advertisement
ConsentStacktype="text/plain", applied automaticallydata-cs-category, override onlyfunctional, analytics, marketing

A few of these have sharp edges worth knowing. Cookiebot accepts combined values like data-cookieconsent="statistics, marketing", but necessary and unclassified are not valid values and should never be used: a necessary script should simply be left unmarked. Pandectes is the odd one out on the type value, using javascript/blocked rather than text/plain. And OneTrust carries its category in a class name rather than a data attribute, which is easy to miss when you are scanning markup for something that looks like a category.

The same analytics tag, three platforms
<!-- Cookiebot -->
<script type="text/plain" data-cookieconsent="statistics"
        src="https://example.com/analytics.js"></script>

<!-- OneTrust (C0002 = performance) -->
<script type="text/plain" class="optanon-category-C0002"
        src="https://example.com/analytics.js"></script>

<!-- Termly -->
<script type="text/plain" data-categories="analytics"
        src="https://example.com/analytics.js"></script>

data-cookieblock-src, and why embeds are different

The text/plain trick only works on script tags, because they are the only ones with a type attribute the browser checks. Iframes and images have no such switch. Set an iframe's type to anything you like and it will still load a YouTube embed, along with the cookies that come with it.

So platforms take the other route: remove the thing that tells the element what to load. Cookiebot's version is data-cookieblock-src. You move the URL out of src and into that attribute, which leaves the element with nowhere to point until consent arrives, at which point Cookiebot moves it back. The reason it is not simply called data-src is that lazy-loading libraries had already claimed that name, and the two would fight over the same attribute.

Blocking a YouTube embed with Cookiebot
<!-- Loads immediately, cookies and all -->
<iframe src="https://www.youtube.com/embed/VIDEO_ID"></iframe>

<!-- Held until marketing consent is given -->
<iframe data-cookieblock-src="https://www.youtube.com/embed/VIDEO_ID"
        data-cookieconsent="marketing"
        frameborder="0" allowfullscreen></iframe>

Auto-blocking versus marking things up by hand

Every platform in that table offers two modes. Auto-blocking watches the page and intercepts anything that matches a maintained list of known trackers, without you touching your markup. Manual markup blocks exactly what you label and nothing else. Most sites end up running both, because auto-blocking handles the recognizable third parties while manual markup covers in-house scripts no list would know about.

Both modes need an escape hatch for scripts that auto-blocking grabs by mistake, and this is where a lot of debugging time goes. Every platform names that escape hatch differently, so the next section decodes each one. If a script you need is silently not running, an over-eager auto-blocker is the first thing to check. Google Tag Manager surfaces the same situation as its own warning, which we unpack in what "a consent management platform may be blocking tags" actually means.

The skip attribute for every major CMP

A skip attribute is the reverse of a blocking attribute: it tells the consent platform's auto-blocker to leave one specific script alone. Cookiebot calls it data-cookieconsent="ignore", OneTrust calls it data-ot-ignore, and ConsentStack calls it data-cs-category="ignore". Some platforms have no markup version at all and handle exemptions from the dashboard instead.

Exempting a script from auto-blocking, by consent platform
PlatformSkip symbolWhat it actually does
Cookiebotdata-cookieconsent="ignore"Exempts the script from automatic blocking only. A tag that was also neutralized manually with type="text/plain" stays blocked
OneTrustdata-ot-ignoreAdded to the script tag with no value. Auto-Blocking passes over the tag entirely
TermlyNone. Dashboard onlyExemptions are set through Domain Rules in the Termly dashboard, not with a markup attribute
CookieYesNone documentedAuto-blocking follows its tracker list. There is no documented skip attribute to put on a tag
ConsentStackdata-cs-category="ignore"Skips blocking and reporting. Use data-cs-category="essential" when a script should run before consent but stay visible in your tracker inventory
Exempting one script, three platforms
<!-- Cookiebot -->
<script data-cookieconsent="ignore"
        src="https://example.com/critical.js"></script>

<!-- OneTrust -->
<script data-ot-ignore
        src="https://example.com/critical.js"></script>

<!-- ConsentStack -->
<script data-cs-category="ignore"
        src="https://example.com/critical.js"></script>

When someone reports that the ignore attribute is not working, it is nearly always one of three things. First, scope: Cookiebot's ignore value only reaches automatic blocking, so a tag that was also neutralized manually with type="text/plain" stays blocked no matter what the attribute says. Second, the attribute has to be on the tag the browser actually sees. If a page builder, a plugin or a tag manager injects the script at runtime, the attribute you wrote in a template never lands on the rendered tag, and the blocker has nothing to read. Third, people debug their source files instead of the rendered DOM. Open the element inspector and look at the tag that is actually on the page; it is often not the one you wrote.

A skip is a compliance decision

Anything you exempt runs for every visitor, before any consent choice, in every region you serve. That is correct for genuinely essential scripts like authentication, payments and fraud prevention, and wrong for anything that sets tracking cookies. Keep the skip list short, and audit it whenever you audit the rest of your consent setup.

Why hand-marking scripts keeps leaking

Manual markup works perfectly on the tags you mark. The problem is that it is a hand-maintained list of things that change without telling you. Someone adds a tag in Google Tag Manager. A plugin update ships a new pixel. A marketer pastes a snippet into a template. A third-party script loads a fourth-party script. None of those arrive with your attribute on them, and none of them will be blocked.

This is why a site can be genuinely well configured and still fail a check. The markup is correct. The coverage is not. And because the tags that slip through are usually the ones added most recently, the gap tends to grow quietly between audits. We measured how often this happens in our State of Cookie Compliance study.

What this looks like at scale

In our scan of 229 sites, 78 to 83 percent sent a request to a third party before the visitor had chosen anything. Among sites where a banner was present and Reject was actually clickable, 43 percent kept a genuine third-party tracker firing after Reject. These are behavioral measurements of what the pages did, not legal verdicts about the sites.

The cookie jar tells the same story after the click. Across 949 sites with a working Reject button, 43% of ghostwritten tracking cookies survived Reject All, and nearly all of them were set before the visitor chose anything.

How ConsentStack handles it

We took the view that if coverage is the failure mode, then hand-marking should be the exception rather than the default. ConsentStack classifies scripts by their domain against a maintained tracker list, so a newly added Meta Pixel or Hotjar tag is recognized and held the first time it appears, whether or not anyone remembered to label it. Blocked tags get the same text/plain treatment, and are restored in their original order once the matching category is granted.

The manual attribute still exists as an override, data-cs-category, for in-house scripts no list would recognize, and data-cs-category="ignore" exempts a script entirely. One honest exception: for platforms that expose a real consent API of their own, such as Google Consent Mode, the script is allowed to load and consent is passed through that API instead, because that is the integration those vendors are built around. If you want to see which of these your own site currently gets right, the free compliance scanner checks it from both an EU and a US vantage point.

Frequently Asked Questions

See what your site actually blocks

Enter your domain and get a free report on what fires before consent and what keeps firing after Reject, checked from both the EU and the US. No signup.

Related Posts