@cookiepal-oss/consent
v1.0.0
Published
Cookie consent banner runtime with script blocking and backend logging.
Downloads
1,517
Readme
@cookiepal-oss/consent
Runtime cookie consent banner. Framework-agnostic, no dependencies beyond what the browser ships.
Install
pnpm add @cookiepal-oss/consentTwo ways to use it
Both entry points accept the same config and return the same ConsentManager. Pick the one that matches how you ship code:
- ESM / bundler →
createConsentManager(named export). Tree-shakeable. - Drop-in
<script>/ IIFE →window.cookiepal.run(browser global). Use this when there's no module loader on the page.
ESM
import { createConsentManager } from '@cookiepal-oss/consent';
createConsentManager({
categories: [
{ slug: 'necessary', required: true },
{ slug: 'analytics' },
{ slug: 'advertisement' },
],
banner: {
notice: { layout: 'box', position: 'bottomLeft', title: 'We value your privacy' },
preferences: { layout: 'center' },
accentColor: '#f59e0b',
},
scriptBlocking: [
{ pattern: 'google-analytics.com', category: 'analytics' },
{ pattern: 'facebook.net', category: 'advertisement' },
],
});Script tag (IIFE)
<script src="https://unpkg.com/@cookiepal-oss/consent/dist/index.iife.js"></script>
<script>
cookiepal.run({
categories: [{ slug: 'necessary', required: true }, { slug: 'analytics' }],
banner: { notice: { layout: 'box', position: 'bottomLeft' } },
});
</script>Under the hood cookiepal.run === createConsentManager.
Config
interface CookiepalConfig {
categories: CategoryConfig[];
banner?: BannerConfig;
scriptBlocking?: ScriptBlockingRule[];
integrations?: IntegrationsConfig;
i18n?: I18n;
backendURL?: string; // optional: POST consents to a backend
siteUrl?: string;
rejectByDefault?: boolean;
preview?: boolean; // render without persisting cookies
debug?: boolean;
}Categories
interface CategoryConfig {
slug: string; // 'necessary', 'analytics', 'advertisement', etc.
required?: boolean; // true → user cannot reject
cookies?: CookieConfig[]; // for the preferences table
}
interface CookieConfig {
name: string;
domain: string;
duration: string; // "2 years", "session", etc.
description?: string;
locale?: Record<string, string>;
}Banner
The banner has two surfaces: the notice (first-visit prompt) and the preferences dialog (per-category toggles). Each picks a layout.
interface BannerConfig {
notice?: NoticeConfig;
preferences?: PreferencesConfig;
fab?: { position?: 'left' | 'right' | 'hidden'; customIcon?: string; tooltipLabel?: string };
accentColor?: string;
titleColor?: string;
textColor?: string;
backgroundColor?: string;
btnTheme?: 'highlighted' | 'transparent';
consentExpiry?: number; // days before re-prompting, default 365
hideBrand?: boolean;
}Notice layouts:
type NoticeConfig =
| { layout: 'banner'; edge?: 'top' | 'bottom'; /* + shared fields */ }
| { layout: 'box'; position?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight'; /* ... */ }
| { layout: 'popup'; /* ... */ };Shared notice fields: title, description, acceptLabel, rejectLabel, customiseLabel, closeLabel, showRejectButton, showCustomiseButton, showCloseButton, cookiePolicyLink.
Preferences layouts:
type PreferencesConfig =
| { layout: 'center' }
| { layout: 'sidebar'; side?: 'left' | 'right' }
| { layout: 'pushDown'; edge?: 'top' | 'bottom' };Shared preferences fields: title, description, acceptLabel, rejectLabel, savePreferencesLabel, showCookieList, googlePrivacyLink, plus table column labels.
API
interface ConsentManager {
getConsent(): ConsentState; // { necessary: true, analytics: false, ... }
acceptAll(): void;
rejectAll(): void;
acceptCategory(category: string): void;
rejectCategory(category: string): void;
showBanner(): void;
hideBanner(): void;
showPreferences(): void;
hidePreferences(): void;
showFab(): void;
hideFab(): void;
on(event: 'consent', cb: (state: ConsentState) => void): void;
reconfigure(patch: Partial<CookiepalConfig> | BannerConfig): void;
setActionHandler(handler: (action: string) => void): void;
destroy(): void;
}Events
const manager = createConsentManager(config);
manager.on('consent', (state) => {
if (state.analytics) loadGoogleAnalytics();
if (state.advertisement) loadMetaPixel();
});Script blocking
Scripts matching a scriptBlocking pattern are held until the matching category is consented:
scriptBlocking: [
{ pattern: 'google-analytics.com', category: 'analytics' },
{ pattern: 'connect.facebook.net', category: 'advertisement' },
]Patterns are substring matches against <script src="..."> URLs.
Integrations
integrations: {
gcm: { enabled: true }, // Google Consent Mode v2
shopify: { enabled: true }, // Shopify consent signals
}Implementations live in @cookiepal-oss/integrations. Each integration maps category consent to the platform's native signals.
i18n
i18n: {
defaultLocale: 'en',
locales: {
en: {
'notice.title': 'We value your privacy',
'notice.button.accept': 'Accept All',
'preferences.button.save': 'Save preferences',
},
es: {
'notice.title': 'Valoramos tu privacidad',
'notice.button.accept': 'Aceptar todo',
},
},
}The banner picks the locale from navigator.language, falling back to defaultLocale. Per-cookie descriptions can be translated via CookieConfig.locale.
Backend logging
Point at a @cookiepal-oss/backend instance:
createConsentManager({
/* … */
backendURL: 'https://consent.yoursite.com',
});Every accept / reject / customise event POSTs { consentId, consent } to <backendURL>/consent. The server stamps IP, User-Agent, timestamp, and edge-provided country / region.
Pre-built bundle
For non-bundler sites, run cookiepal build (from @cookiepal-oss/cli) to produce a single self-contained IIFE bundle with your config baked in. Drop the resulting file onto your site and it's ready.
License
MIT. See repo root.
