Script & embed gating

The core compliance mechanic: nothing that tracks runs before its category is accepted. ConsentLoop gates scripts (external and inline) and embeds (iframes, images, video) with three attributes.

The attributes

AttributeOnMeaning
data-consent="analytics"any elementCategory that must be accepted.
data-consent-src="https://…"script, iframe, img, video…Real src, applied on acceptance.
data-consent-service="ga4"any gated elementOptional: also require this service inside the category.

For scripts, additionally set type="text/plain" so browsers never execute them prematurely. All other attributes (async, defer, ids…) are preserved on activation. Already-activated elements are marked data-consent-activated and never touched twice.

<!-- external script -->
<script type="text/plain" data-consent="analytics" async
        data-consent-src="https://plausible.io/js/script.js"></script>

<!-- inline script -->
<script type="text/plain" data-consent="analytics">
  window.plausible = window.plausible || function () { (plausible.q = plausible.q || []).push(arguments) };
</script>

<!-- service-level: only when analytics AND the "ga4" service are accepted -->
<script type="text/plain" data-consent="analytics" data-consent-service="ga4"
        data-consent-src="https://www.googletagmanager.com/gtag/js?id=G-XXXX"></script>

<!-- embeds: YouTube waits for marketing consent -->
<iframe data-consent="marketing" data-consent-src="https://www.youtube.com/embed/xyz"
        title="Video" loading="lazy"></iframe>

Live demo

This page contains a gated inline script and a gated “embed”. Watch them flip as you change consent — this is the real engine, not a simulation.

blockedinline script data-consent="analytics" — sets window.__demoAnalytics
blockediframe data-consent="marketing"
iframe waits for marketing consent…

SPAs & dynamically added elements

Gating scans the DOM when consent is applied. If your framework injects gated elements later (route changes, lazy components), call:

ConsentLoop.rescan(); // idempotent, cheap — activates anything newly eligible

In React, prefer <ConsentGate> — it renders the real element only after consent, no rescan needed.

Programmatic gating

When an SDK must be booted from code, use service callbacks or events instead of attributes:

ConsentLoop.run({
  categories: {
    analytics: {
      services: {
        posthog: {
          label: "PostHog",
          onAccept: () => import("./analytics").then((m) => m.boot()),
          onReject: () => window.posthog?.opt_out_capturing(),
          cookies: [/^ph_/],   // cleared automatically on withdrawal
        },
      },
    },
  },
});

onAccept fires on every page load while accepted (that’s how you boot), onReject only on an actual withdrawal.

Withdrawal & auto-clear

When a visitor turns a category off, ConsentLoop deletes the cookies you declared in autoClear (and in each service’s cookies) across current and parent domains. Already-running scripts can’t be un-run — if a vendor SDK has no opt-out API, reload after withdrawal:

ConsentLoop.run({
  categories: { analytics: { autoClear: [/^_ga/, "_gid"] } },
  hooks: { onChange: (record, changed) => location.reload() },
});