Documentation
Up and running in 5 minutes
You get the snippet after creating a property, under Property → Integration. It looks like this:
<script src="https://consented.eu/sdk/dist/stub.js" data-block="googletagmanager.com/gtag|connect.facebook.net"></script> <script async src="https://consented.eu/p/YOUR-PROPERTY-ID/cmp.js"></script>
The first line is the loader: synchronous, roughly 2 KB over the wire, and it has to come before every other script — including Google Tag Manager. It blocks the patterns named in data-block immediately and sets the Consent Mode defaults. It reads both off its own script tag, because it runs before the property configuration exists. If you change your services or the Consent Mode switch, paste the snippet again.
Banner language: first the page language from <html lang>, then a data-lang attribute on the script tag (<script async src="…/cmp.js" data-lang="en">) or on <html>, and finally the browser language. Only languages enabled in the property can win. The order and a language menu for visitors are set under Property → Settings.
Declarative — for scripts you embed yourself:
<script type="text/plain" data-consented="google-analytics-4"> gtag('config', 'G-XXXXXXX'); </script>
Pattern-based — for scripts that others embed:
Add URL fragments to each service (e.g. googletagmanager.com/gtag). A MutationObserver strips the src attribute as soon as the element turns up in the document — including deep inside a block inserted later — and puts it back once consent is given.
appendChild, that is a race with the load which the observer is not guaranteed to win. For scripts you control yourself the declarative way is the reliable one; pattern blocking is the safety net for everything else. Embeds — for embedded content with a visible surface, like a video or a map:
<iframe data-consented="youtube-embed" data-consented-src="https://www.youtube-nocookie.com/embed/XXXX" width="560" height="315" title="Video title"></iframe>
No src attribute until consent is given — data-consented names the service directly, data-consented-src carries the actual address. Because the browser never sees a src, there is no race here: unlike pattern blocking above, this way is reliable throughout for embeds, not just a safety net.
// Wait until the state is known Consented.ready(function (state) { if (state && state.services['google-analytics-4']) { startTracking(); } }); // React to changes Consented.on('change', function (state) { console.log(state); }); // Open the detail view — or just put data-consented-open on the element Consented.openSettings(); // Proof of consent Consented.getConsentId();
| Event | When |
|---|---|
consented_ready | Runtime loaded, state known |
consented_update | After every decision the visitor makes |
A ready-made overview of every service and cookie of your property — for the place in your privacy policy where Art. 13 GDPR requires you to inform. Two lines, and it keeps itself current:
<div id="consented-cookie-declaration"></div> <script src="https://consented.eu/p/YOUR-PROPERTY-ID/cookies.js?lang=en"></script>
The div is optional: without it the declaration places itself exactly where the script sits. It brings sparse formatting of its own, everything under the class consented-cd — your CSS overrides it. Pick the language with ?lang=; without it the property default applies.
The same declaration is available as a standalone page for linking and printing:
https://consented.eu/p/YOUR-PROPERTY-ID/cookies?lang=en
Both are built from the published state of your property, not from your draft. What you are editing appears only after you publish — so your privacy policy never names a service the site does not load yet.
Every consent gets an ID that is shown to the visitor in the detail view. With it they can request access or deletion without an account:
GET /api/v1/consent/{consentId}
DELETE /api/v1/consent/{consentId}
Or more conveniently via the public lookup page.