Docs

Script blocking & data-attributes

AdminUpdated Sep 15, 2026

Script blocking & data-attributes

Cookie Munch's core promise is prior blocking: no non-necessary tracker script runs before the visitor has consented. @cookiemunch/core implements this two ways — automatic (packages/core/src/blocker/auto.ts) and manual (packages/core/src/blocker/manual.ts) — that share one reactivation mechanism and one attribute vocabulary, so you can mix them on the same page.

Automatic blocking (data-blockingmode="auto", the default)

With data-blockingmode="auto" on the install tag, the embed calls installAutoBlocker() synchronously, before any per-site config fetch, which:

  1. Monkey-patches Node.prototype.appendChild and Node.prototype.insertBefore — the two paths through which scripts and pixels get inserted, whether by inline HTML parsing, a tag manager's injectScript, or your own JS.

  2. Installs a MutationObserver on document.documentElement to also catch parser-inserted nodes the patched methods don't see directly.

Every inserted SCRIPT, IFRAME, IMG, LINK, EMBED, OBJECT, AUDIO, VIDEO, and SOURCE element — and every matching descendant of an inserted subtree — is checked the instant it's inserted, before the browser fetches or executes it. Each one is classified against a checklist of known tracker domains (Google Analytics, Google/Meta/TikTok/LinkedIn ad pixels, Hotjar, Clarity, Mixpanel, Segment, Matomo, YouTube/Vimeo embeds, and more — see packages/core/src/blocker/checklist.ts), plus any site-specific rules from a completed cookie scan. If the match resolves to a category that isn't yet granted, the element is neutralized in place:

  • <script> elements get type="text/plain" — inert; the browser parses but does not execute them.

  • Everything else gets its src moved to data-cookieblock-src and the real src attribute removed, so the browser never fetches it.

  • Either way, data-cookieconsent="<category>" is written onto the element, recording why it was blocked — the same attribute manual markup uses, so one reactivation path (reactivate() in blocker/manual.ts) handles both.

You write no markup at all for this to work — GTM containers, third-party snippets pasted verbatim, whatever's already on the page gets caught. Notably, the bundled checklist deliberately does not block the GTM container script itself (googletagmanager.com/gtm.js) — blocking GTM wholesale would break Google Consent Mode, which relies on GTM loading and self-throttling its own tags; gtag.js is classified as statistics.

Two escape hatches exist for elements the auto-blocker would otherwise catch:

Mechanism

Effect

data-cookieconsent="ignore" on an element

Never auto-blocked, even if it matches a checklist pattern. Useful for a first-party script whose URL happens to contain a matched string.

ignoreSelectors in the site's blocking config

CSS selectors the auto-blocker never even inspects — a config-level allowlist (set via the dashboard or PUT /v1/sites/:cbid/config), threaded through as ignoreSelectors: () => string[] so it can be updated once the async config fetch resolves.

Elements already neutralized (a <script type="text/plain"> or one already carrying data-cookieblock-src) are skipped, so the blocker is idempotent against elements you mark up by hand.

Manual markup (data-blockingmode="manual")

In manual mode the auto-blocker is never installed; you mark each tracking tag inert yourself, using exactly the attribute vocabulary the auto-blocker writes:

<!-- Single category -->
<script type="text/plain" data-cookieconsent="statistics"
  src="https://www.google-analytics.com/analytics.js"></script>

<!-- Requires BOTH categories granted before it runs -->
<script type="text/plain" data-cookieconsent="statistics,marketing"
  src="https://example.com/combined-pixel.js"></script>

<!-- Non-script resource: real src goes in data-cookieblock-src -->
<iframe data-cookieblock-src="https://www.youtube.com/embed/xyz"
  data-cookieconsent="marketing"></iframe>

Attribute

Applies to

Values

Description

type="text/plain"

<script>

Marks the tag inert. On activation, Cookie Munch doesn't just flip the attribute — it re-creates the node (a fresh <script> with type="text/javascript" and every other attribute copied over) and replaces the old one, because browsers never execute a script element whose type is mutated after insertion.

data-cookieconsent

any blockable element

preferences, statistics, marketing (comma-separated), or ignore

The categories required before the element activates — all listed categories must be granted (an AND, not an OR). ignore opts out of auto-blocking entirely; it is invalid on its own reactivation path (an ignore-only value never activates).

data-cookieblock-src

non-script resources (<iframe>, <img>, <embed>, …)

a URL

Holds the real src while blocked. Restored to src (and this attribute removed) on activation.

data-fc-category

any blockable element

any category id — standard or custom (comma-separated)

Cookie Munch's own alternative to data-cookieconsent, for when the required category isn't one of the three standard toggleable ones.

data-cookieconsent only ever validates the three user-toggleable standard categories (preferences, statistics, marketing) plus the literal ignorenecessary and any custom category id are rejected by isValidMarkupCategory()/ parseMarkupCategories() when read from markup you write. If a site defines custom categories (Banner Studio v2), tag those scripts data-fc-category="my-custom-id" instead — the reactivation logic checks data-fc-category first and falls through to data-cookieconsent only when it's absent, so the two never conflict on one element.

Reactivation runs on every consent change (accept, decline, granular save, or withdrawal re-blocking) and re-scans the document each time, so elements added to the DOM after the fact are picked up on the next consent change, not retroactively — mark up static HTML for this path; use auto-blocking (or your own onConsentChange gating) for dynamically-injected content.

Placeholder elements

For consent-gated embeds where you want to show something instead of silently loading nothing (e.g. "Enable this video to watch"), two class-based hooks need no JS:

<div class="cookieconsent-optin-marketing">
  <!-- shown once marketing is granted -->
</div>
<div class="cookieconsent-optout-marketing">
  Please accept marketing cookies to view this content.
  <!-- shown until marketing is granted -->
</div>

On every reactivation pass, Cookie Munch toggles the hidden attribute on every element matching .cookieconsent-optin-<category> / .cookieconsent-optout-<category> for each of the three toggleable categories (preferences, statistics, marketing) — visible when granted for optin, visible when not granted for optout.

Per-category blocking in practice

Because data-cookieconsent/data-fc-category accept a comma-separated list, you can require more than one category for a single tag — e.g. a combined analytics+ads pixel that should only fire once both statistics and marketing are granted:

<script type="text/plain" data-cookieconsent="statistics,marketing"
  src="https://example.com/combined-pixel.js"></script>

and, for a Banner Studio custom category alongside a standard one:

<script type="text/plain" data-fc-category="statistics,partner-sync"
  src="https://partner.example.com/sync.js"></script>

isCategoryGranted() resolves each id — standard categories against the four core flags, custom ids by looking up their mapsTo in the site's v2 category config — and reactivate() requires every listed id to individually resolve to granted.

Migrating from Cookiebot

Every attribute above — data-cookieconsent, type="text/plain", data-cookieblock-src — is Cookiebot's own attribute vocabulary, verbatim. Combined with the install tag accepting id="Cookiebot" and window.Cookiebot aliasing window.CookieMunch, an existing Cookiebot site with manually-marked-up tags works unmodified after only swapping the <script src> — no HTML changes needed unless you're introducing a custom category, which is the one Cookie Munch-native addition (data-fc-category). The consent cookie reader also tolerates Cookiebot's own relaxed JS-object-literal value format ({stamp:'...',necessary:true,...}, single-quoted, unquoted keys) as a fallback when the value isn't valid JSON (parseCookiebotLegacy() in packages/core/src/cookie.ts), so a value written in Cookiebot's format under the shared cookie name is still understood after migration.

Next: JavaScript API to react to consent changes from your own code, or back to embed scripts for the install tag and its attributes.

Was this page helpful?