Install the banner
Setting up a site takes four steps. The first three happen in the dashboard: create the site, register its domains and publish a banner. The last is adding the script to your site.
1. Create a site
A site is one website, with one banner and one consent log. Plans are priced per site.
2. Register domains
Consent is recorded only from hostnames registered on the site. Add every hostname the script runs on, including www.
A site covers one website: any hostnames under its domain, such as example.no, www.example.no and shop.example.no. Your plan also allows a few alias domains, for example example.com for the same website. A separate website needs its own site.
On self-serve plans, prove you control each domain before the banner goes live there. Until one domain is verified, the script serves nothing and no consent is recorded. Use whichever method suits you; the dashboard shows the token:
| Method | What to add |
|---|---|
| DNS | A TXT record at _pxl-consent.<host> (or at the registrable domain) containing the token |
| Meta tag | <meta name="pxl-consent-verification" content="TOKEN"> on https://<host>/ |
| File | https://<host>/.well-known/pxl-consent-verification.txt containing the token |
Then press Check now. You can remove the record once the domain shows as verified.
3. Publish a banner
The banner editor starts from a compliant default in Norwegian bokmål and English. You can add nynorsk, Swedish, Danish, Finnish, Icelandic, German, French, Spanish, Italian, Dutch, Polish and Portuguese, each with standard texts to check and adjust. Change texts, colours, categories and the privacy policy link, then publish. Every publish is checked, and a banner that fails a blocking check is refused with the reason, for example:
- no one-click reject button on the first layer, next to accept;
- a reject button markedly less visible than accept;
- text colours below WCAG AA contrast (4.5:1);
- a category other than necessary marked as required;
- missing texts in one of the banner's languages;
- a privacy policy link that does not load.
Each publish is a new version. A visitor's earlier choice stays valid until it expires (the consent lifetime, set in the editor), unless you add a category they have not been asked about; then they are asked again. The consent log records which version each choice was made under.
Regimes
| Regime | Behaviour |
|---|---|
| Opt-in | Nothing that needs consent runs until the visitor chooses. Required in the EEA, UK and elsewhere. |
| Opt-out | Everything runs. A persistent "Do not sell or share" control is shown, and a Global Privacy Control signal opts the visitor out. |
| Notice | A dismissible notice with a settings link. Nothing is blocked. |
With automatic selection, the regime is picked per visitor from their country: opt-out in the US and Switzerland, opt-in everywhere else, unless you override countries.
4. Add the script
Put this first inside <head> on every page, before any analytics or advertising tag. The install tab in the dashboard has it with your site ID filled in.
<script src="https://cdn.consent.pxl.as/c/SITE-ID.js" data-cfasync="false"></script>
The script always serves the latest published banner, so the tag does not change when you publish. Load it from the CDN, not from a copy on your own server. A self-hosted copy stops receiving updates, and monitoring reports it as a problem.
The banner language follows data-ccs-lang on the script tag if set, then <html lang>, then the browser's languages, then the banner's default language.
When a page knows its language, say so in the tag as well. The banner is then delivered with that language's texts alone, which makes it smaller. Use the same code in both places:
<script src="https://cdn.consent.pxl.as/c/SITE-ID.js?lang=nb" data-ccs-lang="nb" data-cfasync="false"></script>
A code the banner is not published in falls back to the whole banner, so a wrong value never removes it. The WordPress plugin sends the page language by itself.
Content Security Policy
If your site sends a Content-Security-Policy header, allow the script's origin and the address it records choices to:
script-src https://cdn.consent.pxl.as; connect-src https://in.consent.pxl.as
The banner styles itself with constructed stylesheets, which the policy does not restrict, so style-src needs nothing on current browsers. Browsers without them (Safari before 16.4) fall back to an inline style element and need 'unsafe-inline' in style-src; without it the banner shows unstyled there. Blocked embeds and their placeholders are your own frames, so frame-src is unaffected.
Google Consent Mode v2
Turn on Consent Mode in the banner editor. The script then sets all Google consent types to denied by default and updates them when the visitor chooses:
| Category | Google consent types |
|---|---|
| marketing | ad_storage, ad_user_data, ad_personalization |
| statistics | analytics_storage |
| preferences | functionality_storage, personalization_storage |
The same switch sends the choice to Microsoft Advertising (UET consent mode, ad_storage follows marketing) and Microsoft Clarity (consentv2, with marketing and statistics).
It also pushes events to the data layer, for triggers on tags that do not read Consent Mode. With Consent Mode on, Google's own tags (gtag.js, gtm.js) are not blocked, since they adjust their behaviour to the signals.
Data layer events
| Event | When |
|---|---|
ccs_consent | The state the page runs under: a returning visitor's stored choice, a regime that allows by default, or a new choice. Once per page, unless the state changes. |
ccs_consent_choice | Only when the visitor decides something: accepting, rejecting, saving categories, opting out or withdrawing. Fires again if they choose the same thing again. |
ccs_banner | The banner was shown, the details were opened, or a notice was closed. Nothing is recorded for these; they are for measuring the banner itself. |
The consent events carry:
| Variable | Value |
|---|---|
ccs_purposes | Each category and whether it is allowed, for example {"statistics": true, "marketing": false}. Read one with a data layer variable such as ccs_purposes.statistics. |
ccs_services | Grants for a single service, where a site uses them, without the service: prefix. |
ccs_source | stored for a state the page started with, choice when the visitor just decided. |
ccs_method | accept_all, reject_all, custom, optout, gpc, withdrawal, or stored. |
ccs_consent_id | The consent ID this choice is recorded under, the same one the visitor can be shown. |
ccs_config_version, ccs_policy_version | The banner version and privacy policy version the visitor answered. |
ccs_regime | optin, optout or notice. |
ccs_language | The language the banner was shown in. |
ccs_banner carries ccs_banner_action (shown, customize, optout, notice_dismissed), ccs_regime and ccs_language.
Google Tag Manager
PXL Consent works with Google Tag Manager. Set it up like this:
- Put the PXL Consent script above the Tag Manager snippet in
<head>, and turn on Consent Mode in the banner editor. The script then sets the Consent Mode defaults before Tag Manager loads. For a returning visitor it also sends the stored choice at once, without waiting for the page to finish loading. - Google's own tags, such as Google Analytics 4 and Google Ads, follow Consent Mode by themselves. They wait up to 500 milliseconds (
wait_for_update) for the visitor's state before they send anything. - Tags for other services, such as Meta, LinkedIn or Hotjar, need consent too. If blocking of known services is on, the script holds back their scripts even when Tag Manager adds them to the page. You can also use the tag's consent settings in Tag Manager (Require additional consent for tag to fire), or fire the tag on the custom event
ccs_consentand read the data layer variableccs_purposes. - Server-side tagging needs nothing extra: Google's tags send the visitor's Consent Mode state with each request, so a server container sees it as usual. To send your own fields on, use
ccs_consentas the trigger and map the variables above.
With Consent Mode off, Tag Manager itself is held back until the visitor allows statistics.
If the site can only be changed through Tag Manager, use the PXL Consent tag template instead of the script tag: it sets the defaults and loads the banner from the Consent Initialization trigger. Ask PXL for the template file. Loaded this way, the banner cannot hold back tags that are written into the page's HTML, so prefer the script tag when you can edit the site.
Scripts that delay JavaScript
Speed tools that delay or defer JavaScript must leave the PXL Consent script alone, because it has to run before other scripts. Examples are WP Rocket's Delay JavaScript execution, Perfmatters, LiteSpeed Cache and Cloudflare Rocket Loader. The data-cfasync="false" attribute in the snippet keeps Rocket Loader away from it. In other tools, add the script to the list of exclusions. The WordPress plugin marks its script for the common tools automatically.
Letting visitors change their mind
After a choice, a small settings button stays on the page so the visitor can change or withdraw consent as easily as they gave it. You can also open the banner from your own link with ccs.show(); see the JavaScript API.