Docs

Embed scripts (consent.js)

AdminUpdated Sep 15, 2026

Embed scripts (consent.js)

Cookie Munch protects a site with a single <script> tag. It loads consent.js — a small, dependency-free IIFE built from @cookiemunch/core — which installs prior-blocking, fetches the site's live dashboard config, detects the visitor's region, and renders the banner. Everything else (the JS API, script-blocking markup) builds on top of this one tag.

The install tag

<script id="CookieMunch"
  src="https://cdn.cookiemunch.net/consent.js"
  data-api="https://api.cookiemunch.net"
  data-cbid="your-site-id"
  data-blockingmode="auto"></script>

Place it as early as possible in <head>, before any analytics or marketing tags. consent.js installs its DOM-interception blocker synchronously, on the very first line it executes — but it can only intercept scripts that are inserted after it runs, so placement above everything else is what makes prior-blocking actually prior.

The embed looks up its own <script> element by id, checking #CookieMunch first, then #Cookiebot, then falling back to document.currentScript. Accepting the Cookiebot id is deliberate: an existing Cookiebot install can switch to Cookie Munch by only swapping the src, with no HTML restructuring.

Attributes

Attribute

Required

Values

Description

src

yes

URL

The consent.js bundle. https://cdn.cookiemunch.net/consent.js in the hosted product; point at your own static origin if self-hosting.

data-cbid

yes

string

The site id issued when the site was registered. Read via getAttribute('data-cbid'); with no cbid resolvable from any source, the embed does nothing (bootstrapFromScriptTag/startWithRemoteConfig both return null).

data-blockingmode

no

auto | manual

Only the literal string "auto" enables the DOM-interception blocker; every other value (including omitting the attribute) is treated as manual. See script blocking & data-attributes.

data-api

no

URL (origin)

Overrides the origin used for the config fetch (GET <api>/config/:cbid), consent/impression beacons, the cookie-declaration feed, and the lazily-loaded consent-tcf.js. When omitted, the embed derives the origin from its own src — so data-api is only needed when consent.js is served from a separate static CDN than the API.

data-culture

no

a culture code, e.g. en, fr

Seeds the initial banner language, overriding auto-detection from navigator.language.

There is no data-geolocation or region-forcing attribute on the tag itself — region comes from the server's own visitor-geolocation at config-fetch time and from navigator.globalPrivacyControl client-side, not from a script attribute.

Three ways to set the same three settings

cbid, blockingmode, and culture can each come from three places, resolved with this precedence (highest wins):

  1. data-* attributes on the tag — data-cbid, data-blockingmode, data-culture.

  2. window.CookieMunchConfig — a plain object set before consent.js runs:

    <script>window.CookieMunchConfig = { cbid: 'your-site-id', blockingmode: 'auto' };</script>
    <script src="https://cdn.cookiemunch.net/consent.js"></script>

    This exists because some loaders — notably Google Tag Manager's sandboxed injectScript — cannot set data-* attributes on the tag they inject, but can run arbitrary JS beforehand to set a global.

  3. The src query string...consent.js?cbid=your-site-id&blockingmode=auto. Parsed with new URL(src, location.href), so both absolute and relative src values work.

Each field is resolved independently, so you can mix sources (e.g. data-cbid on the tag plus blockingmode from the global) — resolveScriptConfig picks per-field, not per-source.

What happens on load

The production entry point (startWithRemoteConfig, what consent.js actually runs) does, in order:

  1. Reads the script tag / global / query as above and resolves cbid, blockingmode, culture. No cbid → the embed is a no-op.

  2. Installs the auto-blocker synchronously, if data-blockingmode="auto" and the visitor isn't a recognized crawler — before fetching anything, so there is no window where a script can slip through while the network round-trip to fetch config is in flight.

  3. Fetches GET <api-origin>/config/:cbid to hydrate the real dashboard config (banner content/theme, geo rules, blocking ignoreSelectors, IAB framework settings, visitor region). On any failure — offline, CORS, an outage — it silently keeps the attribute-only base config instead of leaving the page unprotected or throwing.

  4. Builds the CMP from the hydrated config. The very first thing this step does is emit Google Consent Mode's default-denied signal (if enabled), ahead of installing the TCF/GPP/USP stubs, ahead of the banner, and ahead of any vendor tag.

  5. Resolves the banner mode (opt-in / opt-out / off) from geo rules and the visitor's region, honoring Global Privacy Control as a forced decline where it applies.

  6. Renders the banner (or applies implied consent silently, depending on mode) and exposes window.CookieMunch (and the window.Cookiebot alias) — see the JavaScript API.

  7. Beacons anonymous impression (dialogDisplay) and consent-decision events back to the API origin, unauthenticated by design since the embed runs in an untrusted browser context.

Note that steps 4-7 all happen after the config fetch resolves (or fails) — only the auto-blocker in step 2 runs synchronously ahead of the network round-trip. This is sufficient because prior-blocking's job is to stop vendor scripts from running before consent, and those are what step 2 already intercepts; Consent Mode's default signal only needs to precede the vendor tags it configures (e.g. gtag.js/GTM), which the auto-blocker is itself holding back until then.

Two bundles: consent.js vs. consent-tcf.js

@cookiemunch/core's tsup.config.ts emits three build targets from one package: an ESM index.js (consumed by @cookiemunch/react and the server, not shipped to browsers directly), and two browser IIFEs:

  • consent.js — the base embed, built from src/embed.ts, minified, targeting es2020, with a hard 32KB gzip budget enforced in CI. It contains the prior-blocking engine, the banner UI, Google Consent Mode wiring, and lightweight synchronous stubs for __tcfapi / __gpp / __uspapi plus a lazy-loader — but none of the actual IAB TC-string/GVL logic.

  • consent-tcf.js — built from src/embed-tcf.ts with noExternal: [/.*/] (every dependency inlined, no externals), so it is fully self-contained. It carries the heavy @cookiemunch/tcf-derived TCF/GPP/USP encoding logic.

consent.js loads consent-tcf.js lazily, and only when a site has an IAB framework (framework: "iab"), GPP, or US-Privacy enabled in its config — most sites never pay for it. You never reference consent-tcf.js in your own HTML; it's fetched automatically from the same origin as data-api when needed. If you're building or touching @cookiemunch/core, note the dependency direction is one-way and enforced by convention: consent-tcf.js must never be imported from the base embed's module graph, or the 32KB gate breaks.

CDN and self-hosting

In the hosted product, consent.js and consent-tcf.js are served from https://cdn.cookiemunch.net, a separate static origin from the API (https://api.cookiemunch.net) — which is exactly the case data-api exists for. If you self-host the whole stack (docker compose up --build, or packages/server directly), the API server itself serves the built embed, so a bare src pointing at your server is enough and data-api can usually be omitted; the embed derives the API origin from src's own origin when data-api isn't set.

Async, defer, and placement

Do not add async or defer to the install tag. Prior-blocking only protects scripts inserted after consent.js has started executing and patched Node.prototype.appendChild/insertBefore; deferring or asyncing the tag lets the browser parse and potentially start executing later inline/synchronous scripts first, reopening exactly the race the tag exists to close. A plain, synchronous, head-first <script> — as in the install snippet above — is the supported configuration.

Next: JavaScript API for window.CookieMunch, or script blocking & data-attributes for how prior-blocking actually rewrites your tags.

Was this page helpful?