# @pdntechnology/cookie-consent [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![npm version](https://img.shields.io/npm/v/@pdntechnology/cookie-consent.svg)](https://www.npmjs.com/package/@pdntechnology/cookie-consent) A lightweight, open-source **Consent Management Platform (CMP)** for React and Next.js that implements [Google Consent Mode v2](https://developers.google.com/tag-platform/security/guides/consent) the same way certified tools do. No heavy dependencies. No vendor lock-in. Works with Google Tag Manager, GA4, Google Ads, and Meta Pixel. --- ## Why this exists Google-certified CMPs (Cookiebot, OneTrust, CookieYes, …) follow the same technical flow: 1. Set **denied** defaults **before** any Google tag loads 2. Load GTM / gtag.js 3. Show a consent banner 4. On user choice → `gtag('consent', 'update', …)` + dataLayer event 5. When ads are denied → enable `ads_data_redaction` and `url_passthrough` This library implements that flow in ~15 KB of readable JavaScript. > **Note:** Certified CMPs additionally provide IAB TCF v2.2 signals and appear on Google's certified vendor list. This library covers the **Consent Mode v2 technical integration**. You are responsible for privacy policy text and legal compliance. --- ## Features - Google Consent Mode v2 (all 7 signals) - Granular categories: Necessary / Analytics / Marketing - Customizable banner text, theme colors, and cookie inventory - `ads_data_redaction` + `url_passthrough` in bootstrap and on update - Regional default consent (EEA, etc.) - React banner + Next.js bootstrap component - Optional GTM and Meta Pixel loaders (consent-gated) - Framework-agnostic core API (vanilla JS supported) - TypeScript definitions for core API and React components - Error callbacks when consent cannot be saved > Turkish documentation: [README.tr.md](README.tr.md) --- ## Installation ```bash npm install @pdntechnology/cookie-consent ``` ### Next.js setup Add to `next.config.mjs`: ```js const nextConfig = { transpilePackages: ["@pdntechnology/cookie-consent"], }; ``` --- ## Quick start ### 1. Configure (optional) ```js // lib/consent-config.js import { configureConsent } from "@pdntechnology/cookie-consent"; configureConsent({ storageKey: "my_site_consent", // localStorage key waitForUpdateMs: 500, // time GTM waits for consent update adsDataRedaction: true, // Google recommendation when ads denied urlPassthrough: true, // pass click IDs in URL when cookies denied debug: false, }); ``` Import this file in your root layout **before** `ConsentBootstrap`. ### 2. Bootstrap consent defaults (before GTM) ```jsx // app/layout.jsx import "@pdntechnology/cookie-consent/styles.css"; import "@/lib/consent-config"; import { ConsentBootstrap } from "@pdntechnology/cookie-consent/react"; export default function RootLayout({ children }) { return ( {children} ); } ``` ### 3. Banner + tags ```jsx // app/providers.jsx import { CookieConsent, GoogleTagManager, FacebookPixel, } from "@pdntechnology/cookie-consent/react"; export function Providers({ children }) { return ( <> {children} ); } ``` ### 4. Re-open preferences (footer link) ```jsx import { openPreferences } from "@pdntechnology/cookie-consent"; ``` --- ## Customization ### Text and policy URL Every visible string can be overridden via the `labels` prop. The policy link uses `policyHref`. ```jsx ``` | Label key | Default | |-----------|---------| | `title` | Cookie preferences | | `message` | We use cookies to improve your experience… | | `policyLink` | Privacy policy | | `accept` | Accept all | | `reject` | Reject all | | `manage` | Manage preferences | | `save` | Save preferences | | `necessaryTitle` | Necessary | | `necessaryDescription` | Required for core site functionality… | | `analyticsTitle` | Analytics | | `analyticsDescription` | Helps us understand how visitors use the site… | | `marketingTitle` | Marketing | | `marketingDescription` | Used for ads and remarketing… | | `alwaysOn` | Always on | | `cookiesHeading` | View cookies | | `cookieName` | Name | | `cookiePurpose` | Purpose | | `cookieDuration` | Duration | | `cookieProvider` | Provider | | `storageError` | Your preferences could not be saved… | ### Theme colors Pass a `theme` object to override CSS variables without writing custom CSS: ```jsx ``` You can also pass `className` for additional styling hooks. ### Cookie inventory table Provide a `cookies` array to show an expandable table inside each category in the preferences panel: ```jsx ``` If `cookies` is omitted, the banner keeps the simpler three-category view. ### Error handling Consent write functions return a result object: ```js const result = acceptAll(); if (!result.ok) { console.error(result.reason); // "invalid" | "storage" } ``` In React, use callbacks on the banner: ```jsx console.log("saved", consent)} onConsentError={({ reason }) => console.warn("failed", reason)} /> ``` Enable `debug: true` in `configureConsent()` to log warnings to the console, or use `onStorageError` for global handling. --- ## Example app See [`examples/next-app`](examples/next-app) for a working Next.js demo with Turkish labels, custom theme colors, and a cookie inventory table. ```bash cd examples/next-app npm install npm run dev ``` --- | Banner category | User choice | Google Consent Mode signals | |-----------------|-------------|----------------------------| | **Necessary** | Always on | `security_storage: granted` | | **Analytics** | Opt-in | `analytics_storage` | | **Marketing** | Opt-in | `ad_storage`, `ad_user_data`, `ad_personalization`, `functionality_storage`, `personalization_storage` | When marketing is **denied**, the library also sets: - `ads_data_redaction: true` - `url_passthrough: true` --- ## Project structure ``` src/ ├── index.js Public API ├── config.js configureConsent() ├── constants.js Events, signal names ├── preferences.js Parse / serialize user choices ├── storage.js localStorage read/write ├── consent-api.js acceptAll, rejectAll, getConsent, … ├── cookie-schema.js Cookie inventory helpers ├── google/ │ ├── gtag.js dataLayer helpers │ ├── consent-signals.js Category → signal mapping │ ├── consent-lifecycle.js default + update calls │ └── bootstrap-script.js Inline script generator └── react/ ├── ConsentBootstrap.jsx ├── CookieConsent.jsx ├── CookieList.jsx ├── theme.js ├── GoogleTagManager.jsx └── FacebookPixel.jsx ``` --- ## API reference ### Configuration | Function | Description | |----------|-------------| | `configureConsent(config)` | Set storage key, wait time, region defaults | | `getConsentConfig()` | Read active configuration | ### User consent | Function | Description | |----------|-------------| | `getConsent()` | Read stored preferences | | `setConsent({ analytics, marketing })` | Save custom choice; returns `{ ok, consent \| reason }` | | `acceptAll()` | Grant all categories; returns `{ ok, consent \| reason }` | | `rejectAll()` | Deny optional categories; returns `{ ok, consent \| reason }` | | `hasAnalyticsConsent()` | Check analytics opt-in | | `hasMarketingConsent()` | Check marketing opt-in | | `openPreferences()` | Re-open banner panel | | `syncConsentFromStorage()` | Re-apply stored consent after GTM loads | ### Google Consent Mode | Function | Description | |----------|-------------| | `getConsentBootstrapScript()` | Inline script for vanilla HTML | | `applyConsentDefaults()` | Set denied defaults programmatically | | `applyConsentUpdate(consent)` | Push user choice to gtag | | `buildGoogleConsentState(consent)` | Get signal object | ### Events | Constant | Fired when | |----------|------------| | `EVENT_CONSENT_UPDATED` | User saves a choice | | `EVENT_OPEN_PREFERENCES` | Preferences panel should open | | `GTM_CONSENT_EVENT` | Pushed to dataLayer (`cookie_consent_update`) | ### `CookieConsent` props | Prop | Type | Description | |------|------|-------------| | `labels` | `object` | Override banner text | | `theme` | `object` | Override banner colors | | `cookies` | `CookieEntry[]` | Optional cookie inventory table | | `policyHref` | `string` | Privacy / cookie policy URL | | `LinkComponent` | `component` | Custom link component (default: Next.js `Link`) | | `className` | `string` | Extra CSS class on the banner root | | `onConsentSaved` | `(consent) => void` | Called after a successful save | | `onConsentError` | `({ reason }) => void` | Called when save fails | --- ## Vanilla HTML (no React) ```html ``` Or generate the script programmatically: ```js import { getConsentBootstrapScript } from "@pdntechnology/cookie-consent"; console.log(getConsentBootstrapScript()); ``` --- ## GTM integration checklist 1. Enable **Consent Mode** in your GTM container settings 2. Configure GA4 / Google Ads tags to require appropriate consent signals 3. Optional: create a trigger on custom event `cookie_consent_update` 4. Test with [Tag Assistant](https://tagassistant.google.com/) --- ## Regional defaults (EEA example) ```js configureConsent({ regionDefaults: [ { regions: ["AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "IS", "LI", "NO"], ad_storage: "denied", analytics_storage: "denied", ad_user_data: "denied", ad_personalization: "denied", }, ], }); ``` --- ## Troubleshooting | Problem | Likely cause | Fix | |---------|--------------|-----| | Banner never appears | Consent already stored | Clear `localStorage` key or call `openPreferences()` | | GTM fires before consent | Bootstrap missing or loaded too late | Add `` with `beforeInteractive` before GTM | | Consent not saved | Storage blocked (private mode, Safari ITP) | Handle `onConsentError`; show inline error from banner | | Tags still fire after reject | GTM tags not using Consent Mode | Enable Consent Mode in GTM and set tag consent requirements | | TypeScript errors on `./react` | Missing types path | Import from `@pdntechnology/cookie-consent/react` (types included since v1.1) | --- ```bash # In this repo cd cookie-consent npm link # In your Next.js app npm link @pdntechnology/cookie-consent ``` Or use a file dependency: ```json { "dependencies": { "@pdntechnology/cookie-consent": "file:../cookie-consent" } } ``` --- ## Publishing to npm ```bash npm login npm publish --access public ``` --- ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). Bug reports and pull requests are welcome. --- ## License [MIT](LICENSE) © [PDN Technology](https://pdntechnology.com)