Cookiebot captures the user’s category choices, but those categories never reach your custom loader or tag manager — so scripts you inject yourself keep running (or stay blocked) regardless of what the user actually chose in the banner.

Cookiebot’s auto-blocking rewrites <script> tags it recognizes, which lulls teams into assuming everything is gated. But any script you inject at runtime, hand off to Google Tag Manager, or load through your own consent gate is invisible to auto-blocking. Those consumers need the consent state pushed to them explicitly, and Cookiebot exposes exactly the events and object to do that.

Triage: Confirm Categories Never Reach Your Loader

If the consent booleans flip correctly but your custom-loaded tags ignore them, the root cause below applies.

Cookiebot ships two independent mechanisms, and teams conflate them. Auto-blocking works by rewriting known third-party <script> tags — Cookiebot changes their type to text/plain and adds a data-cookieconsent category, then re-enables them once the matching category is granted. This only governs static tags present in the HTML that Cookiebot recognizes. The consent object plus its events is the programmatic interface: window.Cookiebot.consent holds the current booleans, and Cookiebot dispatches DOM events when that object changes.

Any script your application injects at runtime — a loader gated behind a feature flag, a tag pushed through Google Tag Manager, an SDK you import() dynamically — never passes through auto-blocking’s HTML rewrite. Auto-blocking simply cannot see it. If your only integration is auto-blocking, those runtime consumers run ungoverned, which is both a compliance gap and the reason your loader ignores the user’s decision.

The correct model is the one described in the CMP selection and integration guide: Cookiebot is the single source of truth, and every consumer — including your custom loader — subscribes to its change events and reads its consent object rather than assuming auto-blocking covers them. When several vendors depend on that state, route it through a broker as covered in syncing consent states across multiple vendors; the subscription primitive is identical either way.

Cookiebot exposes three DOM events on window, plus the Cookiebot.consent object:

  • CookiebotOnConsentReady — fires once the consent state is known, on every page load. This is where load-time replay of a returning visitor’s saved decision belongs. Fires whether the user accepts, declines, or a prior decision is restored from the cookie.
  • CookiebotOnAccept — fires when the user accepts one or more categories. Read Cookiebot.consent inside it to see exactly which.
  • CookiebotOnDecline — fires when categories are declined. Use it to skip or tear down anything gated on a now-denied category.

The consent object has one boolean per category: Cookiebot.consent.necessary, .preferences, .statistics, and .marketing.

Resolution: Subscribe to Cookiebot Events, Gate on the Booleans

Define one apply function that reads Cookiebot.consent and injects only what is granted, then wire it to CookiebotOnConsentReady (load-time replay) and CookiebotOnAccept/CookiebotOnDecline (live changes).

// consent-sync-cookiebot.js — load AFTER the Cookiebot loader script tag.
// The Cookiebot script tag itself carries data-cbid="YOUR-DOMAIN-GROUP-ID".

// 1. Map Cookiebot's category booleans to your injection gates.
//    Cookiebot's categories are fixed: necessary, preferences,
//    statistics, marketing.
function currentConsent() {
  // Cookiebot.consent may not exist until the SDK is ready; default to
  // all-denied so nothing leaks before the state is known.
  const c = (window.Cookiebot && window.Cookiebot.consent) || {};
  return {
    statistics: c.statistics === true,
    marketing:  c.marketing === true,
    preferences: c.preferences === true,
  };
}

// 2. Idempotent apply: safe to call from every event.
const injected = new Set();
function applyConsent() {
  const consent = currentConsent();

  if (consent.statistics && !injected.has('statistics')) {
    injected.add('statistics');
    injectScript('https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX'); // your GA4 id
  }
  if (consent.marketing && !injected.has('marketing')) {
    injected.add('marketing');
    injectScript('https://connect.facebook.net/en_US/fbevents.js');
  }
  // Add further categories as needed. A false boolean must never inject.
}

function injectScript(src) {
  const s = document.createElement('script');
  s.src = src;
  s.async = true;
  document.head.appendChild(s);
}

// 3. Subscribe. CookiebotOnConsentReady runs on every load once state is
//    known — this replays a returning visitor's saved decision.
window.addEventListener('CookiebotOnConsentReady', applyConsent);

// CookiebotOnAccept / CookiebotOnDecline fire on live user changes.
window.addEventListener('CookiebotOnAccept', applyConsent);
window.addEventListener('CookiebotOnDecline', function () {
  // Re-evaluate: a declined category should not inject. Teardown of an
  // already-loaded SDK is handled separately (see revocation guide).
  applyConsent();
});

For static tags you do want Cookiebot to auto-block, mark them declaratively instead of injecting them — set type="text/plain" and the category:

<!-- Cookiebot auto-blocks this until the marketing category is granted -->
<script
  type="text/plain"
  data-cookieconsent="marketing"
  src="https://connect.facebook.net/en_US/fbevents.js">
</script>

Use the declarative data-cookieconsent form for tags that live in your HTML, and the event-driven applyConsent() for anything you inject at runtime. The two approaches coexist; the mistake is assuming auto-blocking covers the runtime case.

Live revocation of a category whose SDK already loaded needs an explicit teardown rather than just skipping injection — that path is covered in handling consent revocation without a page reload.

Verification

A marketing request that appears only after accept, combined with Cookiebot.consent.marketing flipping from false to true, confirms the sync works end to end.

Common Pitfalls

  • Assuming auto-blocking covers runtime-injected scripts. Auto-blocking only rewrites tags present in the initial HTML that Cookiebot recognizes. Anything you createElement('script') or load through GTM at runtime bypasses it entirely and must be gated on Cookiebot.consent.
  • Reading Cookiebot.consent before CookiebotOnConsentReady. The object may be absent or stale until the ready event fires. Reading it during your loader’s synchronous init returns undefined booleans, so gates evaluate to denied and nothing ever loads. Read it inside the event handlers.
  • Registering listeners after Cookiebot has already dispatched. If your subscription code runs after CookiebotOnConsentReady has fired, you miss the load-time replay. Register the listeners as early as possible, or call applyConsent() once immediately after registering as a catch-up.

Related

Up: Selecting and Integrating a Consent Management Platform