Introduktion
CookiePilot er en samtykkeplatform til cookies (CMP), der er i overensstemmelse med GDPR (databeskyttelsesforordningen), den danske cookiebekendtgørelse (udstedt med hjemmel i lov om elektroniske kommunikationsnet og -tjenester) og Google Consent Mode v2. I Danmark føres tilsyn med disse regler af Datatilsynet.
Hvem det er til
- Websiteejere: udrulning uden en linje kode.
- Udviklere: API, events, GTM-integrationer.
- Bureauer: white-label og styring af flere domæner.
Hurtig start
Trin 1: Opret en konto
- Opret en konto på app.cookiepilot.io/register.
- Tilføj dit domæne i panelet og kopiér API-nøglen (formatet
cp_live_...).
Trin 2: Installer koden
En direkte installation er to scripts i <head>, i denne rækkefølge.
Trin 2a: Standardsamtykke (inline stub). Sæt dette ind først, før ethvert andet script (cookiepilot.js, GA, GTM, reklame- og sporingstags):
<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>
Dette inline-snippet sætter med det samme hver kategori til denied (med wait_for_update: 500), så standardtilstanden i Google Consent Mode v2 er klar, før noget andet indlæses. For en tilbagevendende besøgende læser det det gemte samtykke direkte fra cookien og affyrer et consent update.
Trin 2b: Bannerscript. Tilføj dette lige efter inline-stubben:
<!-- CookiePilot -->
<script async src="https://cdn.cookiepilot.io/cookiepilot.js" data-cpkey="TWOJ_KLUCZ"></script>
Trin 3: Konfigurer banneret
I panelet: Domæne → Konfiguration → Udseende:
- bannerets placering (top, bund, modal),
- farver og tekster,
- en flydende knap „Cookieindstillinger“ til tilbagevendende besøgende.
Integration med Google Tag Manager
Gennem GTM installerer du CookiePilot med et enkelt Custom HTML-tag, der sætter standardsamtykketilstanden og indlæser banneret i den tidligste GTM-fase. Du har hverken brug for en separat stub-fil eller et andet tag.
Tag: CookiePilot - Consent Init + Banner
I GTM opretter du et Custom HTML-tag (Tags → New → Custom HTML) og indsætter:
<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. Tagget skal affyres én gang pr. side. Google-tags (GA4, Google Ads), Facebook Pixel og andre marketingtags bruger IKKE triggeren Consent Initialization; de affyres senere (Consent Checks eller en samtykkeafhængig trigger). Gem tagget og publicér GTM-containeren.
Rækkefølge og triggere
| Rækkefølge | Tag | Trigger |
|---|---|---|
| 1 | CookiePilot - Consent Init + Banner | Consent Initialization - All Pages |
| 2 | GA4, Google Ads, UET | All Pages (Consent Mode håndterer selv samtykket) |
| 3 | Facebook Pixel, TikTok, LinkedIn osv. | Custom Event cookiepilot_consent_update + samtykkebetingelse (se nedenfor) |
Ikke-Google-tags (Facebook Pixel, TikTok, LinkedIn)
Google Consent Mode dækker kun Google-tags. For hvert andet script pusher widget'en en event til dataLayer ved hver samtykkeændring:
dataLayer.push({
event: 'cookiepilot_consent_update',
cookiepilot_consent: {
necessary: true,
analytics: true,
marketing: true,
preferences: false
}
});
Eventen affyres også ved hvert besøg fra en tilbagevendende bruger (når widget'en læser samtykkecookien), så GTM-triggeren virker ved hvert besøg, ikke kun ved den første beslutning.
Fælles mønster (gør dette én gang)
Du konfigurerer disse tre elementer én gang og genbruger dem derefter til alle ikke-Google-tags.
- Trigger (Triggers → New → Custom Event): Event name
cookiepilot_consent_update. Ingen betingelser, ingen „Once per page“ (tagget skal kunne affyres igen efter en ændret beslutning). - Data Layer Variable for hver kategori, du bruger:
- Name
cookiepilot_consent.marketing→ variabel f.eks.dlv.cp_marketing - Name
cookiepilot_consent.analytics→ variabel f.eks.dlv.cp_analytics - Name
cookiepilot_consent.preferences→ variabel f.eks.dlv.cp_preferences
- Name
- Trigger Group eller en betingelse på triggeren:
dlv.cp_marketing equals true(for marketingtags) eller det relevante felt.
I hvert af eksemplerne nedenfor er Trigger den samme Custom Event med en betingelse tilføjet på den rigtige variabel.
Facebook Pixel
Tag i 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+ betingelsedlv.cp_marketing equals true. - Tag firing options: Once per page.
Eventuelt, for fuld overholdelse af Facebook Limited Data Use, tilføj et andet tag, der kalder fbq('consent','revoke') med triggeren dlv.cp_marketing equals false.
TikTok Pixel
Tag i 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+ betingelsedlv.cp_marketing equals true. - Tag firing options: Once per page.
ttq.grantConsent() er TikToks nye API (introduceret i 2024). Uden det modtager TikTok hashede data uden samtykke, hvilket bryder betingelserne.
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+ betingelsedlv.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+ betingelsedlv.cp_analytics equals true. - Tag firing options: Once per page.
Hotjar hører under analytics, ikke marketing (det måler adfærd, ikke reklamer). Tjek din egen cookiepolitik, nogle virksomheder klassificerer Hotjar anderledes.
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+ betingelsedlv.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+ betingelsedlv.cp_marketing equals true. - Tag firing options: Once per page.
Microsoft Ads (UET)
UET har understøttet Google Consent Mode siden slutningen af 2023, så du har ikke brug for en Custom Event-trigger. Tilføj tagget på den sædvanlige måde (All Pages, Once per page), og UET læser selv ad_storage fra GCM, som widget'en sætter.
Kategorisammenkobling (oversigt)
| Tag | Kategori | Felt i cookiepilot_consent |
|---|---|---|
| Facebook Pixel | marketing | marketing |
| TikTok Pixel | marketing | marketing |
| LinkedIn Insight | marketing | marketing |
| marketing | marketing | |
| Hotjar | analytics | analytics |
| Microsoft Clarity | analytics | analytics |
| Mixpanel, Amplitude | analytics | analytics |
| Intercom, Drift, Crisp | preferences | preferences |
| GA4, Google Ads, UET | (GCM, ingen trigger) | håndteres af gtag('consent','update') |
API-reference
CookiePilot udstiller objektet window.CookiePilot:
| Metode | Beskrivelse |
|---|---|
CookiePilot.getConsent() | Den aktuelle samtykketilstand, eller null hvis der endnu ikke er truffet en beslutning. |
CookiePilot.acceptAll() | Giver samtykke til alle kategorier. |
CookiePilot.rejectAll() | Afviser alt undtagen necessary. |
CookiePilot.updateConsent(partial) | Opdaterer udvalgte kategorier, f.eks. { analytics: true }. |
CookiePilot.showSettings() | Åbner indstillingsmodal'en. |
CookiePilot.hideSettings() | Lukker indstillingsmodal'en. |
CookiePilot.showMyConsent() / hideMyConsent() | Viser/skjuler den flydende knap. |
Eksempel
const consent = CookiePilot.getConsent();
// { necessary: true, analytics: true, marketing: false, preferences: false }
CookiePilot.updateConsent({ analytics: true });
document.getElementById('cookie-settings').addEventListener('click', () => {
CookiePilot.showSettings();
});
Link „Administrer cookies“ i footeren
<a href="#" onclick="CookiePilot.showSettings(); return false;">Administrer cookies</a>
JavaScript-events
Widget'en dispatcher en native cookiepilot:consent-event på window. Lytteren skal registreres inden widget'en indlæses, hvis du vil fange eventen for en tilbagevendende besøgende (dispatchen affyres straks, når widget'en starter):
<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 indeholder samme payload som getConsent(). Til GTM-baserede integrationer skal du bruge den dataLayer-event, der er beskrevet ovenfor, i stedet for denne, fordi dataLayer er et persistent array, og GTM opsamler historiske events.
Samtykkekategorier
| Kategori | Beskrivelse | Standard |
|---|---|---|
necessary | Nødvendige for at websitet virker | Altid slået til |
analytics | Statistik og analyse | Kræver samtykke |
marketing | Reklame og remarketing | Kræver samtykke |
preferences | Personalisering, sprog | Kræver samtykke |
Sammenkobling med Google Consent Mode
| CookiePilot-kategori | Consent Mode-felter |
|---|---|
analytics | analytics_storage |
marketing | ad_storage, ad_user_data, ad_personalization |
preferences | functionality_storage, personalization_storage |
| (altid) | security_storage: granted |
Google Consent Mode v2
Version 2 tilføjede ad_user_data og ad_personalization (påkrævet siden marts 2024 i EU/EØS for Google-annoncering).
| Parameter | Beskrivelse |
|---|---|
ad_storage | Reklamecookies |
analytics_storage | Analysecookies |
ad_user_data | Afsendelse af brugerdata til Google |
ad_personalization | Personalisering af reklamer |
functionality_storage | Funktionelle cookies |
personalization_storage | Personaliseringscookies |
security_storage | Altid granted |
Sådan virker det:
- Stubben sætter synkront hvert felt til
denied, medwait_for_update: 500. - Efter brugerens beslutning affyrer widget'en
gtag('consent', 'update', {...})ved hjælp af sammenkoblingen ovenfor. - For en tilbagevendende besøgende affyres trin 2 straks, når widget'en starter, ud fra cookien.
Konfiguration af udseende
I panelet: Domæne → Konfiguration:
- Udseende: placering, farver, layout (BAR / BOX / MODAL).
- Tekster: overskrift, beskrivelse, knapetiketter, kategoribeskrivelser. 13 sprog (EN, PL, DE, FR, ES, IT, NL, PT, SV, CS, RO, EL, HU).
- Samtykkeknap: en flydende knap „Cookieindstillinger“, der vises efter den første beslutning (nederst til venstre/højre).
- Custom CSS: et felt til dine egne styles. Widget'en renderes i Shadow DOM, så CSS-selektorer fra hoveddokumentet virker ikke. Brug kun dette felt.
Tilgængelighed (WCAG 2.1 AA)
- ✅ Tastaturnavigation (Tab, Shift+Tab, Enter, Escape).
- ✅ ARIA-labels, role="dialog", aria-modal.
- ✅ Focus trap i modal'en.
- ✅ Understøttelse af skærmlæsere (live regions ved tilstandsændring).
- ✅ Responsivt design.
Integrationer
WordPress
Vi har et officielt plugin: CookiePilot på WordPress.org.
- WordPress-admin → Plugins → Tilføj nyt → søg efter „CookiePilot“.
- Installer og aktivér.
- Indstillinger → CookiePilot → indsæt API-nøglen fra panelet.
Pluginet indsætter selv stubben og tagget i <head> (i den rigtige rækkefølge, foran andre scripts) og leverer en [cookiepilot_settings]-shortcode til et „Administrer cookies“-link i footeren.
Hvis du foretrækker at undlade pluginet, så brug „Insert Headers and Footers“ og indsæt snippet fra trin 2 i Hurtig start i Header-sektionen.
Shopify
- Butik → Temaer → Rediger kode.
- Indsæt snippet før
</head>itheme.liquid.
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" sikrer, at bannerscriptet kører tidligt. Husk, at inline-stubben fra Trin 2a (standardsamtykke) tilføjes separat, i <head> foran dette script, for eksempel via next/script med dangerouslySetInnerHTML eller direkte i app/layout.tsx.
FAQ
Gør scriptet websitet langsommere?
Bundlen fylder omkring 12 KB gzippet og indlæses asynkront. Ingen indvirkning på Core Web Vitals.
Hvor længe gemmes samtykket?
Brugerens beslutningscookie: 365 dage som standard (kan konfigureres i panelet). Analyseevents: 2 år (ClickHouse TTL).
Affyres eventen cookiepilot_consent_update for tilbagevendende besøgende?
Ja. Widget'en læser cookien ved start og pusher eventen til dataLayer. Custom Event-triggeren i GTM affyres ved hvert besøg, ikke kun ved den første beslutning.
Hvor rapporterer jeg et problem?
kontakt@cookiepilot.io eller chatten i panelet.
Testtjekliste efter udrulning
Når CookiePilot er installeret, skal du verificere konfigurationen, før du starter reklamekampagner.
1. Test før brugeren giver samtykke
- Åbn websitet i inkognitotilstand og ryd cookies for domænet.
- Åbn DevTools → Network og genindlæs siden.
- Før du klikker på samtykke, skal du bekræfte, at:
- der ikke oprettes analyse-/marketingcookies, f.eks.
_ga,_gcl_*,_fbp,_ttp, - marketingtags ikke sender forespørgsler til Meta/TikTok/LinkedIn før marketingsamtykke,
dataLayerhar standardtilstandendeniedforad_storage,ad_user_data,ad_personalizationoganalytics_storage.
- der ikke oprettes analyse-/marketingcookies, f.eks.
2. Test efter accept af samtykke
- Klik „Accepter alle“.
- Bekræft i DevTools, at eventen
cookiepilot_consent_updateer dukket op. - For Google-tags: tjek i GTM Preview / Tag Assistant, at Consent Mode skiftede tilstanden til
grantedfor de relevante kategorier. - For GA4: tjek DebugView for at bekræfte, at events begynder at ankomme efter samtykke.
- For Meta Pixel / TikTok / LinkedIn: tjek, at tags kun affyres efter eventen
cookiepilot_consent_updateog betingelsen om marketingsamtykke.
3. Test afvisning af samtykke
- Ryd cookies og genindlæs siden.
- Klik „Afvis alle“.
- Bekræft, at marketingcookies og forespørgsler stadig ikke udløses.
- Bekræft, at de essentielle sitefunktioner stadig virker.
4. Almindelige fejl
- GA4/Google Ads-tagget affyres på
Consent Initializationi stedet for en senere trigger. - Meta Pixel eller TikTok Pixel har ingen betingelse på
cookiepilot_consent.marketing. - En gammel, hardkodet tracker ligger stadig i
<head>før CookiePilot. - Dokumentationen eller skabelonen har stadig en forældet script-URL eller en gammel identifikator-attribut i stedet for det aktuelle
cookiepilot.jsmeddata-cpkey.
Samtykkebeviser, eksporter og DSAR
CookiePilot registrerer samtykkeevents, så du kan rekonstruere konteksten for en besøgendes beslutning. En optegnelse kan indeholde tidspunktet for eventen, eventtypen, domænet, side- eller kilde-URL, samtykkeversionen, kategorierne og samtykkestrengen, når den er tilgængelig, anonymiseret IP-adresse, hash af user-agent, enhedstype og browser.
Eksporter
- Bevis for en besøgendes samtykke: JSON, CSV eller printvenlig HTML.
- Auditlog for domænet: CSV eller JSON for et valgt datointerval.
- Eksporter er afgrænset til din organisation og til de domæner, du har adgang til.
DSAR-workflow
En Owner eller Admin kan starte en anmodning om dataindsigt fra en samtykkerække eller fra DSAR-siden i panelet. Panelet finder den besøgendes events, viser intervallet og lader dig downloade bevisfiler. En anmodning om sletning kræver en begrundelse og logges i organisationens aktivitetsspor.
Begrænsninger
Auditeksporter for domænet bruger det valgte datointerval og kan blive afkortet for meget store resultatsæt. Opbevaring af samtykkeloggen følger den aktive plan og kontopolitikken, så den offentlige dokumentation lover ikke et fast antal år.