Prerequisites
- A domain added in Katla
- At least one completed scan
Installation
Copy the tag from Install → Widget in your dashboard and paste it before</head>:
<head>, which is what gets the Google
Consent Mode default declared before your Google tag can initialise. It then fetches the
engine, which carries your cookie inventory and the banner.
You can also get it from the CLI with katla install example.com.
If you would rather link it, {siteId}.js serves the same loader:
The loader
theme or debug parameter you put on it is
carried across. Resolution happens on our side: an exact match, then the language, then
English, so an unrecognised tag never fails.
Add locale to skip detection and pin one language:
gtag.js routinely beats -
see Google Consent Mode.
Winning that race matters only if your Google tag loads before consent at all. It does not have
to, and on katla.app it does not: basic consent mode holds the tag back
until the visitor allows analytics, so nothing - not even a cookieless ping carrying their IP
and page URL - reaches Google from someone who declined. Read that section before you decide;
the loader advice above applies either way.
Linked, the loader needs a request of its own before the cookie guard exists. Inline it
(below) and that request disappears - the loader is already on the page, and the only fetch
left is the engine.
Inlining the loader
The loader is small enough to paste into the page instead of linking to it. Copy it from the Install page in your dashboard, under Widget → Inline, and put it in<head>:
<head>, so the
Google Consent Mode default is declared before your Google tag can initialise. If you have ever
seen _ga appear before the banner did, this is the fix.
Pinning a language makes the snippet smaller still, because locale handling drops out of it
entirely:
Self-hosting
Download{siteId}.consent.js and serve it from your own origin. It carries every language,
so there is nothing else to fetch and nothing to keep in sync.
If you use Google Consent Mode, take {siteId}.js - the loader - as well, and serve the pair
side by side under matching names:
.js - so as long as the
two names match it will find your copy rather than reaching back to our CDN. Re-download both
when you change your cookie inventory or widget settings.
Inlining and self-hosting compose, and together they mean no request to us at all. An inlined
loader has no URL of its own, so it cannot resolve a sibling and has to be told where the engine
is: enter its address in Install → Widget → Inline → Self-hosted engine URL and it is baked
into the snippet. The file can be called anything.
Script parameters
You can customize the script behavior with query parameters:Content Security Policy
If your site sends aContent-Security-Policy header, script-src must allow
https://cdn.katla.app, and connect-src must allow https://consent.katla.app so
consent records can be stored. On the free plan, img-src must also allow
https://cdn.katla.app for the Katla logo in the banner.
script-src treats it as one. Under a nonce-based policy,
put your nonce on the tag:
script-src, and re-hash it
whenever you re-copy the snippet. If neither fits your policy, link the loader instead: the
{siteId}.js tag needs no inline allowance at all.
How it works
- The script installs a cookie guard that intercepts
document.cookiewrites. - If the visitor hasn’t given consent yet, the widget appears.
- The visitor chooses their preferences (accept all, reject all, or select categories).
- A
_katla_consentcookie is stored for 365 days with their choice. - Non-consented cookies are blocked and any existing disallowed cookies are removed.
Functional cookies are always allowed and cannot be rejected.
Blocking scripts and pixels
The cookie guard stops cookies from being written. It does not stop a tracker from loading, and a pixel such as the Meta Pixel reports the visit whether or not it can set a cookie. To hold a tag back until the visitor allows its category, mark it up:data-katla-category is one of analytics, marketing, personalization or security. When the page loads with that category already allowed, or as soon as the visitor allows it, Katla runs each held-back script in document order. It waits for an external script to load before running the next tag, so a vendor’s inline setup can call the library loaded above it.
A tag that has run stays running. If the visitor later narrows their consent, the cookie guard enforces the new choice straight away and the tags stay held back from the next page load.
If your site adds held-back tags after the page has loaded, for example on a client-side route change, call KatlaConsent.activateBlocked() to pick them up.
On WordPress, the Katla plugin writes this markup for you and runs it itself.
Customization
You can customize the widget appearance in your site settings. Available options:
Both light and dark mode colors can be configured independently.
Colors are your site’s branding rather than a banner setting: they live on the site’s
Appearance page in the dashboard, and the accessibility widget
uses the same ones. After your site’s first scan, Katla suggests a palette read from your
homepage there, adjusted so text and buttons stay readable. Nothing changes on your site
until you save it.
What each color paints
Everything else - the border, the row divider, the panel behind an open cookie table, the
hover wash, the off state of a switch, and the label color on each button - is derived from
those six. The border in particular has no setting of its own: it is the background and the
text mixed, so a colored card gets a border that belongs to it. The text on a button is
computed from its fill, so a pale secondary gets dark text rather than becoming unreadable.
Border radius is a single dial rather than a pixel value. The dashboard shows it as the
corner of the card itself, at twice the stored value, and the widget fans the dial out into
five radii in proportion: the card at 2×, a centered card at 2.25×, a category card at 1.5×,
a button at 1.25× and the small ghost controls at 1×. A dial of 8 - 16px - is the redesign’s
own geometry; 0 gives square corners throughout.
Equal buttons
Reject and Accept all always occupy one row of two equal columns, at the same height, radius and font weight, in every layout and every language - that is what prominence is assessed on, and no setting changes it. Equal buttons removes the remaining difference by painting Reject in the primary color too. Turning on DMA Compliant implies it. It applies to that pair only. “Save preferences” in the preference center keeps the secondary color either way: it commits whatever the visitor set above rather than being the “no” half of a choice, so painting it primary alongside “Accept all” would leave two equally loud buttons doing different things. When Regulation is set toauto, the widget detects the visitor’s region at runtime using timezone and GPC signals. See the CCPA guide and DMA guide for details.
When Google Consent Mode is enabled, the widget script automatically signals consent state to Google Analytics and Google Ads tags. See the Google Consent Mode guide for details.
Other embed formats
Besides the JavaScript widget, Katla provides additional endpoints for your site:Example
Widget example
A Vite multipage example that loads the hosted widget and embeds the policy page.