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
| Attribute | On | Meaning |
|---|---|---|
data-consent="analytics" | any element | Category that must be accepted. |
data-consent-src="https://…" | script, iframe, img, video… | Real src, applied on acceptance. |
data-consent-service="ga4" | any gated element | Optional: 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.
data-consent="analytics" — sets window.__demoAnalyticsdata-consent="marketing"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() },
});