Documentation

CookiePilot Documentation

The complete guide to installing, configuring and integrating CookiePilot with your site.

Introduction

CookiePilot is a consent management platform (CMP), compliant with the GDPR, the ePrivacy rules (the PECR in the UK) and Google Consent Mode v2.

Who it's for

  • Site owners: deploy with no coding required.
  • Developers: API, events, GTM integrations.
  • Agencies: white-label, multi-domain management.

Quick start

Step 1: Sign up

  1. Create an account at app.cookiepilot.io/register.
  2. Add your domain in the dashboard and copy the API key (format cp_live_...).

Step 2: Install the code

A direct install is two scripts in the <head>, in this order.

Step 2a: Default consent (inline stub). Paste this first, before any other script (cookiepilot.js, GA, GTM, advertising and tracking tags):

<script>"use strict";(function(){window.dataLayer=window.dataLayer||[];var d={ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",functionality_storage:"denied",personalization_storage:"denied",security_storage:"granted",wait_for_update:500},h=false;try{for(var i=0;i<window.dataLayer.length;i++){var x=window.dataLayer[i];if(x&&x[0]==="consent"&&x[1]==="default"){h=true;break}}}catch(err){}if(!h&&window.dataLayer.length)window.dataLayer.unshift(["consent","default",d]);window.gtag=function(){window.dataLayer.push(arguments)};if(!h)window.gtag("consent","default",d);var a=document.cookie.match(/(^|)cookiepilot_consent=([^;]+)/);if(a){try{var e=JSON.parse(decodeURIComponent(a[2]));window.gtag("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:e.marketing?"granted":"denied",ad_user_data:e.marketing?"granted":"denied",ad_personalization:e.marketing?"granted":"denied",functionality_storage:e.preferences?"granted":"denied",personalization_storage:e.preferences?"granted":"denied",security_storage:"granted"})}catch(err){}}})();</script>

This inline snippet immediately sets every category to denied (with wait_for_update: 500), so the default Google Consent Mode v2 state is ready before anything else loads. For a returning visitor it reads the saved consent straight from the cookie and fires a consent update.

Step 2b: Banner script. Add this right after the inline stub:

<!-- CookiePilot -->
<script async src="https://cdn.cookiepilot.io/cookiepilot.js" data-cpkey="TWOJ_KLUCZ"></script>

Step 3: Configure the banner

In the dashboard: Domain → Configuration → Appearance:

  • banner position (top, bottom, modal),
  • colours and copy,
  • a floating "Cookie settings" button for returning visitors.

Google Tag Manager integration

Through GTM you install CookiePilot with a single Custom HTML tag that sets the default consent state and loads the banner at the earliest GTM phase. You don't need a separate stub file or a second tag.

In GTM, create a Custom HTML tag (Tags → New → Custom HTML) and paste:

<script>"use strict";
(function() {
window.dataLayer=window.dataLayer||[];var d={ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",functionality_storage:"denied",personalization_storage:"denied",security_storage:"granted",wait_for_update:500},h=false;try{for(var i=0;i<window.dataLayer.length;i++){var x=window.dataLayer[i];if(x&&x[0]==="consent"&&x[1]==="default"){h=true;break}}}catch(err){}if(!h&&window.dataLayer.length)window.dataLayer.unshift(["consent","default",d]);window.gtag=function(){window.dataLayer.push(arguments)};if(!h)window.gtag("consent","default",d);var a=document.cookie.match(/(^|)cookiepilot_consent=([^;]+)/);if(a){try{var e=JSON.parse(decodeURIComponent(a[2]));window.gtag("consent","update",{analytics_storage:e.analytics?"granted":"denied",ad_storage:e.marketing?"granted":"denied",ad_user_data:e.marketing?"granted":"denied",ad_personalization:e.marketing?"granted":"denied",functionality_storage:e.preferences?"granted":"denied",personalization_storage:e.preferences?"granted":"denied",security_storage:"granted"})}catch(err){}}
var s = document.createElement('script');
s.src = 'https://cdn.cookiepilot.io/cookiepilot.js?cpkey=' + encodeURIComponent('TWOJ_KLUCZ');
document.head.appendChild(s);
})();
</script>

Trigger: Consent Initialization - All Pages. The tag must fire once per page. Google tags (GA4, Google Ads), Facebook Pixel and other marketing tags do NOT use the Consent Initialization trigger; they fire later (Consent Checks or a consent-dependent trigger). Save the tag and publish the GTM container.

Order and triggers

OrderTagTrigger
1CookiePilot - Consent Init + BannerConsent Initialization - All Pages
2GA4, Google Ads, UETAll Pages (Consent Mode handles consent itself)
3Facebook Pixel, TikTok, LinkedIn, etc.Custom Event cookiepilot_consent_update + consent condition (see below)

Non-Google tags (Facebook Pixel, TikTok, LinkedIn)

Google Consent Mode only covers Google tags. For every other script, the widget pushes an event to the dataLayer on each consent change:

dataLayer.push({
  event: 'cookiepilot_consent_update',
  cookiepilot_consent: {
    necessary: true,
    analytics: true,
    marketing: true,
    preferences: false
  }
});

The event also fires on every visit from a returning user (when the widget reads the consent cookie), so the GTM trigger works on every visit, not just on the first decision.

Shared pattern (do this once)

You configure these three pieces once, then reuse them for all non-Google tags.

  1. Trigger (Triggers → New → Custom Event): Event name cookiepilot_consent_update. No conditions, no "Once per page" (the tag must be able to fire again after a changed decision).
  2. Data Layer Variable for each category you use:
    • Name cookiepilot_consent.marketing → variable e.g. dlv.cp_marketing
    • Name cookiepilot_consent.analytics → variable e.g. dlv.cp_analytics
    • Name cookiepilot_consent.preferences → variable e.g. dlv.cp_preferences
  3. Trigger Group or a condition on the trigger: dlv.cp_marketing equals true (for marketing tags) or the relevant field.

In each of the examples below, the Trigger is the same Custom Event with a condition added on the right variable.

Facebook Pixel

Tag in GTM: Tags → New → Custom HTML.

<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'YOUR_PIXEL_ID');
fbq('track', 'PageView');
</script>
  • Trigger: cookiepilot_consent_update + condition dlv.cp_marketing equals true.
  • Tag firing options: Once per page.

Optionally, for full compliance with Facebook Limited Data Use, add a second tag that calls fbq('consent','revoke') with the trigger dlv.cp_marketing equals false.

TikTok Pixel

Tag in GTM: Tags → New → Custom HTML.

<script>
!function (w, d, t) {
  w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie","holdConsent","revokeConsent","grantConsent"],ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);ttq.instance=function(t){for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e},ttq.load=function(e,n){var r="https://analytics.tiktok.com/i18n/pixel/events.js",o=n&&n.partner;ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=r,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};n=document.createElement("script");n.type="text/javascript",n.async=!0,n.src=r+"?sdkid="+e+"&lib="+t;e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(n,e)};
  ttq.load('YOUR_TIKTOK_PIXEL_ID');
  ttq.grantConsent();
  ttq.page();
}(window, document, 'ttq');
</script>
  • Trigger: cookiepilot_consent_update + condition dlv.cp_marketing equals true.
  • Tag firing options: Once per page.

ttq.grantConsent() is TikTok's new API (introduced in 2024). Without it, TikTok receives hashed data without consent, which breaks the terms.

LinkedIn Insight Tag

<script type="text/javascript">
_linkedin_partner_id = "YOUR_LINKEDIN_PARTNER_ID";
window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];
window._linkedin_data_partner_ids.push(_linkedin_partner_id);
</script>
<script type="text/javascript">
(function(l) {
if (!l){window.lintrk = function(a,b){window.lintrk.q.push([a,b])};
window.lintrk.q=[]}
var s = document.getElementsByTagName("script")[0];
var b = document.createElement("script");
b.type = "text/javascript";b.async = true;
b.src = "https://snap.licdn.com/li.lms-analytics/insight.min.js";
s.parentNode.insertBefore(b, s);})(window.lintrk);
</script>
  • Trigger: cookiepilot_consent_update + condition dlv.cp_marketing equals true.
  • Tag firing options: Once per page.

Hotjar

<script>
(function(h,o,t,j,a,r){
  h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
  h._hjSettings={hjid:YOUR_HOTJAR_ID,hjsv:6};
  a=o.getElementsByTagName('head')[0];
  r=o.createElement('script');r.async=1;
  r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
  a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-',".js?sv=");
</script>
  • Trigger: cookiepilot_consent_update + condition dlv.cp_analytics equals true.
  • Tag firing options: Once per page.

Hotjar goes under analytics, not marketing (it measures behaviour, not ads). Check your own cookie policy — some companies classify Hotjar differently.

Microsoft Clarity

<script type="text/javascript">
(function(c,l,a,r,i,t,y){
  c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
  t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
  y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "YOUR_CLARITY_PROJECT_ID");
</script>
  • Trigger: cookiepilot_consent_update + condition dlv.cp_analytics equals true.
  • Tag firing options: Once per page.

Pinterest Tag

<script>
!function(e){if(!window.pintrk){window.pintrk = function () {
window.pintrk.queue.push(Array.prototype.slice.call(arguments))};var
n=window.pintrk;n.queue=[],n.version="3.0";var
t=document.createElement("script");t.async=!0,t.src=e;var
r=document.getElementsByTagName("script")[0];
r.parentNode.insertBefore(t,r)}}("https://s.pinimg.com/ct/core.js");
pintrk('load', 'YOUR_PINTEREST_TAG_ID');
pintrk('page');
</script>
  • Trigger: cookiepilot_consent_update + condition dlv.cp_marketing equals true.
  • Tag firing options: Once per page.

Microsoft Ads (UET)

UET has supported Google Consent Mode since late 2023, so you don't need a Custom Event trigger. Add the tag the standard way (All Pages, Once per page) and UET reads ad_storage from GCM itself, which the widget sets.

Category mapping (summary)

TagCategoryField in cookiepilot_consent
Facebook Pixelmarketingmarketing
TikTok Pixelmarketingmarketing
LinkedIn Insightmarketingmarketing
Pinterestmarketingmarketing
Hotjaranalyticsanalytics
Microsoft Clarityanalyticsanalytics
Mixpanel, Amplitudeanalyticsanalytics
Intercom, Drift, Crisppreferencespreferences
GA4, Google Ads, UET(GCM, no trigger)handled by gtag('consent','update')

API Reference

CookiePilot exposes the window.CookiePilot object:

MethodDescription
CookiePilot.getConsent()The current consent state, or null if no decision has been made.
CookiePilot.acceptAll()Consent to all categories.
CookiePilot.rejectAll()Rejects everything except necessary.
CookiePilot.updateConsent(partial)Updates selected categories, e.g. { analytics: true }.
CookiePilot.showSettings()Opens the preferences modal.
CookiePilot.hideSettings()Closes the preferences modal.
CookiePilot.showMyConsent() / hideMyConsent()Shows/hides the floating button.

Example

const consent = CookiePilot.getConsent();
// { necessary: true, analytics: true, marketing: false, preferences: false }

CookiePilot.updateConsent({ analytics: true });

document.getElementById('cookie-settings').addEventListener('click', () => {
  CookiePilot.showSettings();
});
<a href="#" onclick="CookiePilot.showSettings(); return false;">Manage cookies</a>

JavaScript events

The widget dispatches a native cookiepilot:consent event on window. The listener must be registered before the widget loads if you want to catch the event for a returning visitor (the dispatch fires immediately once the widget starts):

<script>
  window.addEventListener('cookiepilot:consent', (e) => {
    if (e.detail.marketing) {
      fbq('init', 'YOUR_PIXEL_ID');
    }
  });
</script>
<script src="https://cdn.cookiepilot.io/cookiepilot.js" data-cpkey="TWOJ_KLUCZ"></script>

e.detail contains the same payload as getConsent(). For GTM-based integrations, use the dataLayer event described above instead of this one, because the dataLayer is a persistent array and GTM picks up historical events.


CategoryDescriptionDefault
necessaryRequired for the site to workAlways on
analyticsStatistics and analyticsConsent required
marketingAdvertising and remarketingConsent required
preferencesPersonalisation, languageConsent required
CookiePilot categoryConsent Mode fields
analyticsanalytics_storage
marketingad_storage, ad_user_data, ad_personalization
preferencesfunctionality_storage, personalization_storage
(always)security_storage: granted

Version 2 added ad_user_data and ad_personalization (required since March 2024 in the EU/EEA for Google advertising).

ParameterDescription
ad_storageAdvertising cookies
analytics_storageAnalytics cookies
ad_user_dataSending user data to Google
ad_personalizationAd personalisation
functionality_storageFunctional cookies
personalization_storagePersonalisation cookies
security_storageAlways granted

How it works:

  1. The stub sets every field to denied synchronously, with wait_for_update: 500.
  2. After the user's decision, the widget fires gtag('consent', 'update', {...}) using the mapping above.
  3. For a returning visitor, step 2 fires immediately once the widget starts, based on the cookie.

Appearance configuration

In the dashboard: Domain → Configuration:

  • Appearance: position, colours, layout (BAR / BOX / MODAL).
  • Copy: heading, description, button labels, category descriptions. 13 languages (EN, PL, DE, FR, ES, IT, NL, PT, SV, CS, RO, EL, HU).
  • Consent button: a floating "Cookie settings" button shown after the first decision (bottom left/right).
  • Custom CSS: a field for your own styles. The widget renders in the Shadow DOM, so CSS selectors from the main document won't work. Use this field only.

Accessibility (WCAG 2.1 AA)

  • ✅ Keyboard navigation (Tab, Shift+Tab, Enter, Escape).
  • ✅ ARIA labels, role="dialog", aria-modal.
  • ✅ Focus trap in the modal.
  • ✅ Screen reader support (live regions on state change).
  • ✅ Responsive design.

Integrations

WordPress

We have an official plugin: CookiePilot on WordPress.org.

  1. WordPress admin → Plugins → Add New → search for "CookiePilot".
  2. Install and activate.
  3. Settings → CookiePilot → paste the API key from the dashboard.

The plugin inserts the stub and the tag into the <head> itself (in the correct order, ahead of other scripts) and provides a [cookiepilot_settings] shortcode for a "Manage cookies" link in the footer.

If you prefer not to use the plugin, use "Insert Headers and Footers" and paste the snippet from Step 2 of the Quick start into the Header section.

Shopify

  1. Store → Themes → Edit code.
  2. In theme.liquid, paste the snippet before </head>.

Next.js

// app/layout.tsx
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <head>
        <Script
          src="https://cdn.cookiepilot.io/cookiepilot.js"
          data-cpkey="TWOJ_KLUCZ"
          strategy="beforeInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

strategy="beforeInteractive" ensures the banner script runs early. Remember that the inline stub from Step 2a (default consent) is added separately, in the <head> ahead of this script — for example via next/script with dangerouslySetInnerHTML or directly in app/layout.tsx.


FAQ

Does the script slow down the site?

The bundle is around 12 KB gzipped and loads asynchronously. No impact on Core Web Vitals.

The user's decision cookie: 365 days by default (configurable in the dashboard). Analytics events: 2 years (ClickHouse TTL).

Yes. The widget reads the cookie on start and pushes the event to the dataLayer. The Custom Event trigger in GTM fires on every visit, not just on the first decision.

Where do I report a problem?

kontakt@cookiepilot.io or the chat in the dashboard.


Post-deployment testing checklist

Once CookiePilot is installed, verify the configuration before launching advertising campaigns.

  1. Open the site in incognito mode and clear cookies for the domain.
  2. Open DevTools → Network and reload the page.
  3. Before clicking consent, confirm that:
    • no analytics/marketing cookies are created, e.g. _ga, _gcl_*, _fbp, _ttp,
    • marketing tags don't send requests to Meta/TikTok/LinkedIn before marketing consent,
    • the dataLayer has the default denied state for ad_storage, ad_user_data, ad_personalization and analytics_storage.
  1. Click "Accept all".
  2. In DevTools, check that the cookiepilot_consent_update event appeared.
  3. For Google tags, check in GTM Preview / Tag Assistant that Consent Mode switched the state to granted for the relevant categories.
  4. For GA4, check DebugView to confirm events start arriving after consent.
  5. For Meta Pixel / TikTok / LinkedIn, check that the tags fire only after the cookiepilot_consent_update event and the marketing consent condition.
  1. Clear cookies and reload the page.
  2. Click "Reject all".
  3. Confirm that marketing cookies and requests are still not triggered.
  4. Confirm that essential site features still work.

4. Common mistakes

  • The GA4/Google Ads tag fires on Consent Initialization instead of a later trigger.
  • The Meta Pixel or TikTok Pixel has no condition on cookiepilot_consent.marketing.
  • An old, hardcoded tracker is still in the <head> before CookiePilot.
  • The documentation or template still has an outdated script URL or an old identifier attribute instead of the current cookiepilot.js with data-cpkey.