485 lines
13 KiB
Markdown
485 lines
13 KiB
Markdown
# @pdntechnology/cookie-consent
|
|
|
|
[](LICENSE)
|
|
[](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 (
|
|
<html>
|
|
<body>
|
|
<ConsentBootstrap />
|
|
{children}
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 3. Banner + tags
|
|
|
|
```jsx
|
|
// app/providers.jsx
|
|
import {
|
|
CookieConsent,
|
|
GoogleTagManager,
|
|
FacebookPixel,
|
|
} from "@pdntechnology/cookie-consent/react";
|
|
|
|
export function Providers({ children }) {
|
|
return (
|
|
<>
|
|
{children}
|
|
<CookieConsent
|
|
policyHref="/privacy"
|
|
labels={{
|
|
accept: "Accept all",
|
|
reject: "Reject all",
|
|
manage: "Manage preferences",
|
|
}}
|
|
/>
|
|
<GoogleTagManager gtmId="GTM-XXXXXXX" />
|
|
<FacebookPixel pixelId="123456789" />
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 4. Re-open preferences (footer link)
|
|
|
|
```jsx
|
|
import { openPreferences } from "@pdntechnology/cookie-consent";
|
|
|
|
<button type="button" onClick={openPreferences}>
|
|
Cookie settings
|
|
</button>
|
|
```
|
|
|
|
---
|
|
|
|
## Customization
|
|
|
|
### Text and policy URL
|
|
|
|
Every visible string can be overridden via the `labels` prop. The policy link uses `policyHref`.
|
|
|
|
```jsx
|
|
<CookieConsent
|
|
policyHref="/privacy"
|
|
labels={{
|
|
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: "Google Analytics (_ga, _gid).",
|
|
marketingTitle: "Marketing",
|
|
marketingDescription: "Google Ads and Meta Pixel.",
|
|
alwaysOn: "Always on",
|
|
storageError: "Your preferences could not be saved. Please try again.",
|
|
}}
|
|
/>
|
|
```
|
|
|
|
| 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
|
|
<CookieConsent
|
|
theme={{
|
|
background: "rgba(15, 23, 42, 0.97)",
|
|
text: "#f8fafc",
|
|
textMuted: "rgba(248, 250, 252, 0.82)",
|
|
link: "#93c5fd",
|
|
primary: "#2563eb",
|
|
primaryText: "#ffffff",
|
|
secondaryBackground: "rgba(255, 255, 255, 0.1)",
|
|
border: "rgba(255, 255, 255, 0.14)",
|
|
accent: "#2563eb",
|
|
}}
|
|
/>
|
|
```
|
|
|
|
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
|
|
<CookieConsent
|
|
cookies={[
|
|
{
|
|
category: "necessary",
|
|
name: "my_site_consent",
|
|
purpose: "Stores your consent choice",
|
|
duration: "1 year",
|
|
provider: "This site",
|
|
},
|
|
{
|
|
category: "analytics",
|
|
name: "_ga",
|
|
purpose: "Visitor statistics",
|
|
duration: "2 years",
|
|
provider: "Google Analytics",
|
|
},
|
|
]}
|
|
/>
|
|
```
|
|
|
|
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
|
|
<CookieConsent
|
|
onConsentSaved={(consent) => 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 <head> 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
|
|
<head>
|
|
<script>
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag('consent', 'default', {
|
|
ad_storage: 'denied',
|
|
analytics_storage: 'denied',
|
|
ad_user_data: 'denied',
|
|
ad_personalization: 'denied',
|
|
functionality_storage: 'denied',
|
|
personalization_storage: 'denied',
|
|
security_storage: 'granted',
|
|
wait_for_update: 500
|
|
});
|
|
</script>
|
|
<!-- GTM snippet here -->
|
|
</head>
|
|
```
|
|
|
|
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 `<ConsentBootstrap />` 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)
|