Consentl for any website
Add one script tag first in your site's <head>. The banner, texts, regions, Consent Mode and blocking rules are all configured in the Consentl dashboard.
- Loader first in <head>
- Yes
Consentl works on any site that lets you add a <script> to <head>. There is nothing to install
on your server: everything is configured in the Consentl dashboard (external site) and
delivered by the loader script.
What each plan does
| Plan | What Consentl does on your site |
|---|---|
| Free | Banner and preferences, consent log, "Cookie settings" link, and manual blocking: only the tags you mark yourself (section 6) wait for consent. |
| Pro | Everything in Free, plus Google Consent Mode v2: defaults before any Google tag and an update with every choice. Other trackers are not held back automatically. |
| Business | Everything in Pro, plus automatic blocking of known trackers (Meta Pixel, TikTok, Hotjar, Clarity, LinkedIn and more) and blocking rules you add in the dashboard. |
On Free, Google tags, Meta Pixel and every other tag run as soon as the page loads unless you mark them manually.
1. Add your site and copy the key
- Sign in to the dashboard (or create a free account) and add your site.
- Add every hostname the site runs on in the site's Domains field.
www.and the bare domain count as the same site; add staging domains separately. - Open the site's Setup page and copy the install snippet. The site key in it starts with
sk_and is public: it identifies your site, it is not a password.
2. Install the loader
<script src="https://api.consentl.com/v1/loader.js?k=SITE_KEY&l=LANG"></script>
SITE_KEY: your site key (sk_+ 24 characters).LANG: the page language, the same value as<html lang>:en,de,fr,pt-PT,pt-BR,fr-CA, … Region variants fall back automatically (de-AT→de). Leave out&l=to use the site's default language from the dashboard.
Where: as the first element in <head>, directly after <meta charset>. It must come before
Google Tag Manager, gtag.js, Meta Pixel, analytics, chat widgets and A/B testing tools.
How: synchronous: no async, no defer, no type="module". The script is small and cached.
Why first and synchronous:
- Google Consent Mode v2 defaults (Pro and higher) must be set before any Google tag reads them.
- Blocking stops tags from running until the visitor agrees. Scripts above the loader have already run.
- A returning visitor's choice is applied at once: no flash of the banner and no tags firing before the choice is known.
If your platform cannot put the script first, its guide explains exactly what that means.
What blocking can and cannot stop
- Tags loaded by code (Google Tag Manager, a pixel's JavaScript loader, scripts your app adds) are held back before they are requested when they match a known tracker (Business) or one of your blocking rules.
- Tags written in the page HTML (
<script src>,<img>pixels,<iframe>) are requested by the browser before any script can stop them: while the loader downloads, the browser already fetches what it finds further down the page. Consentl still keeps a recognised script from running, but the request, with the visitor's IP address, the page address and any third-party cookies, has gone out; for a tracking pixel the request is the tracking. Mark every tag written in the HTML manually (section 6) and delete<noscript>tracking pixels. - Server-rendered sites can also inline the loader, which removes the download wait. Tags written in the HTML still need to be marked.
3. Check that it works
- Open the site in a private window. The banner appears.
- In the browser's developer tools → Console:
Consentl.get()returns the current choices;Consentl.region()returns the legal model applied to the visitor, such as{ mode: "opt_in", law: "gdpr", gpc: false, known: true }.
- In Network, before you accept:
- no requests to tracking hosts from tags loaded by code, when automatic blocking is on (Business); with Consent Mode, only Google's cookieless pings;
- tags written in the HTML that you have not marked are requested. Mark them and check again.
- While the site is in test mode, add
?consentl=previewto an address to see the banner without storing or logging a choice. It does not pick a region: the dashboard's Preview shows the banner for each region.
Regions: on each page view the banner asks Consentl for the visitor's country, which comes from the visitor's IP address. The lookup happens in memory and stores nothing, and the loader stays the same for every visitor. Your rule for that country applies:
- US states are not told apart: a US visitor gets your rule for the whole US or, without one, your first US-state rule (for example California).
- When the country is unknown, your rule for unknown regions applies (opt-in by default).
- The code is in the
dataLayerfieldconsentl.region(for exampleDEorUS). To see another region's banner, use the dashboard Preview or a VPN.
4. Add a "Cookie settings" link
Withdrawing consent must be as easy as giving it. Use any of these:
<a href="#consentl">Cookie settings</a>
<button type="button" data-consentl-open>Cookie settings</button>
window.Consentl.open("prefs");
Links whose address ends in #consentl or #consentl-preferences, and any element with
data-consentl-open, open the preferences instead of navigating, also on elements added later.
Other anchors that only start the same way (such as #consentl-api) are left alone. The dashboard
can also show a floating preferences icon.
5. Cookie declaration, privacy requests and EU withdrawal form
<div data-consentl-declaration data-api="https://api.consentl.com" data-key="SITE_KEY" data-locale="LANG"></div>
<div data-consentl-request data-api="https://api.consentl.com" data-key="SITE_KEY" data-locale="LANG"></div>
<div data-consentl-withdrawal data-api="https://api.consentl.com" data-key="SITE_KEY" data-locale="LANG"></div>
<script src="https://api.consentl.com/v1/blocks.js" defer></script>
Include blocks.js once per page, after the <div>s. The declaration lists the cookies on the
dashboard's Cookies page: the scanner adds what it finds when the site is created, when you click
Scan now and, on Business and higher, every month; you can add or edit entries yourself (details are
filled in from the Open Cookie Database).
Privacy requests are verified by e-mail and appear in the dashboard with their deadline. The EU
withdrawal form needs the Pro plan or higher and must be switched on in the dashboard; otherwise
its <div> stays empty.
6. Hold back anything that is not recognised automatically
<script type="text/plain" data-consentl="marketing" src="https://ads.example.com/tag.js"></script>
<script type="text/plain" data-consentl="analytics">/* inline code */</script>
<iframe data-consentl="marketing" data-src="https://www.youtube-nocookie.com/embed/ID" title="Video"></iframe>
<img data-consentl="marketing" data-src="https://tracker.example.com/pixel.gif" alt="" width="1" height="1">
- Categories:
preferences,analytics,marketing. - Nothing is requested until the category is granted. This is the only way to hold back a tag written in the HTML.
- Add
data-consentl-type="module"to a marked script that must run as a module. - Manual blocking works on every plan. The Business plan adds automatic blocking of known trackers and blocking rules ("URL contains") you add in the dashboard without touching the site. Rules match the address of scripts, iframes and images, and inline scripts that load something. Page data of frameworks such as Next.js, Nuxt, Remix or SvelteKit is never held back.
- A held tag runs once after consent, even if your framework adds a second copy of it while the page starts (Nuxt does).
Single-page apps
- Nothing to call on route changes: the runtime is loaded once per document, the choice is stored
in the first-party
consentlcookie, and held-back tags are released without a reload. - Routers that replace the page body (Astro
<ClientRouter />, Turbo, Swup, htmxhx-boost) keep the banner, the floating icon and "Cookie settings" links working. - Keep the loader address the same on every page and do not load it again on navigation. If your builder offers "load on every page", choose "load once".
- Tags your app inserts later are held back too when they match a known tracker (Business) or a
blocking rule. For your own code, check consent.
consentl:changefires on every page view with the current choices and again on every change, so start things only once:
let started = false;
const maybeStart = (c) => {
if (c.analytics && !started) {
started = true;
startAnalytics();
}
};
document.addEventListener("consentl:change", (e) => maybeStart(e.detail));
if (window.Consentl) maybeStart(window.Consentl.get());
- After a language switch without a full reload, the banner keeps its language until the next page load.
With Google Tag Manager
The Consentl GTM template can own Google Consent Mode. If you use it and the snippet,
add &gcm=ext to the loader address, so only the template sends Consent Mode commands:
<script src="https://api.consentl.com/v1/loader.js?k=SITE_KEY&l=LANG&gcm=ext"></script>
Inline bootstrap (server-side)
If your pages are rendered on a server you control, you can print the loader inline instead of linking it, as the Consentl plugins for WordPress, Shopware, PrestaShop and Magento do:
- On the server, fetch
https://api.consentl.com/v1/loader.js?k=SITE_KEY&l=LANG. - Cache the response per site key and language for 5 minutes (its
Cache-Control); if a later fetch fails, keep serving the last good copy. - Print
<script>+ the response +</script>as the first element in<head>. With a Content Security Policy, add your nonce (the content changes with your settings, so a hash does not work). - With no cached copy at all, print the normal
<script src="…">tag.
// Node, simplified
const cache = new Map(); // url -> { body, until }
async function consentlHead(key, lang) {
const url = `https://api.consentl.com/v1/loader.js?k=${encodeURIComponent(key)}&l=${encodeURIComponent(lang)}`;
const hit = cache.get(url);
if (!hit || hit.until < Date.now()) {
try {
const r = await fetch(url);
if (r.ok) cache.set(url, { body: await r.text(), until: Date.now() + 300_000 });
} catch {
/* keep the stale copy */
}
}
const body = cache.get(url)?.body;
return body ? `<script>${body}</script>` : `<script src="${url}"></script>`;
}
Do not change the response: it is generated by Consentl for your site. Print it in the HTML template your server writes, outside any client-side framework. Not for the Next.js App Router: React would send the loader twice on every page there; use the plain tag as shown in Next.js.
JavaScript API
| Call | Returns / does |
|---|---|
Consentl.get() |
Current choices: { preferences, analytics, marketing, sale } (stored choice, or the region default) |
Consentl.stored() |
The visitor's stored decision ({ id, ts, rev, c, a }), or null before a decision |
Consentl.allowed("analytics") |
true / false for preferences, analytics, marketing or sale |
Consentl.region() |
{ mode, law, gpc, known } once the region rule is applied, else null |
Consentl.open("prefs") |
Opens the preferences ("banner" for the banner); false when the banner could not be loaded |
Consentl.uiFailed |
true once the banner could not be loaded on this page (an ad blocker blocks it) |
Consentl.acceptAll(), Consentl.rejectAll() |
Records a decision, like the banner buttons |
Consentl.on("change", fn), Consentl.on("ready", fn) |
Same as the DOM events below |
Events on document (the choices are in event.detail):
consentl:change: on every page view once the region rule is applied, and on every change;consentl:ready: once per page view, after the firstconsentl:change;consentl:ui-error: the banner could not be loaded (also after one retry). Links to#consentlthen act as normal links, so you can show your own fallback.
The dataLayer gets consentl_consent (every page view and every change) and consentl_update
(changes only), each with consentl.preferences, analytics, marketing, sale, stored and
region. Marked elements: data-consentl="<category>", type="text/plain", data-src,
data-consentl-srcset (images with srcset), data-consentl-type. Add data-consentl-noicon to an element on pages that have their own
"Cookie settings" button, to hide the floating icon there.
Content Security Policy
If your site sends a Content-Security-Policy header, allow the Consentl API:
script-src 'self' https://api.consentl.com;
connect-src 'self' https://api.consentl.com;
img-src 'self' https://api.consentl.com data:;
style-src 'self' 'unsafe-inline';
style-src 'unsafe-inline'is needed because the banner adds its styles at runtime. Contact support if your policy forbids inline styles.- Scripts that Consentl releases after consent still need their own hosts in
script-src. - With a strict nonce policy, give the loader tag your nonce and use
'strict-dynamic', so the scripts it loads and releases are trusted. Without'strict-dynamic', give marked inline scripts your nonce too. - Subresource Integrity (SRI) cannot be used on the loader, because its content changes when you change settings in the dashboard.
Server-side checks (optional)
The choice is stored in the first-party cookie consentl:
1.<id>.<timestamp>.<revision>.<flags>.<action>. flags has four digits for preferences,
analytics, marketing and sale (1 = granted). No cookie means no decision yet: treat it as "not
granted". A cookie with an older revision (after "Reset all consents") no longer counts. Pages that
depend on this cookie must not be stored in a shared cache. Prefer client-side gating
(section 6) where you can.
Limitations
- The banner cannot control scripts that run before the loader: scripts above it in
<head>, and scripts a hosting platform injects outside your control. - Tags written in the page HTML are requested before consent unless you mark them.
- Embedded third-party iframes (videos, maps) are held back as a whole until consent. Consentl cannot see what happens inside them.
- Consentl does not implement the IAB Transparency & Consent Framework (TCF).
Related guides
Stuck? Write to [email protected] — we answer in English on business days.