npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

headless-consent

v0.1.0

Published

Framework-agnostic, type-safe consent engine with a headless API and TCF bridge.

Readme

headless-consent

Framework-agnostic, strongly typed consent engine for cookie banners and privacy preferences.

headless-consent provides:

  • Consent state machine (unknown, accepted_all, rejected_all, custom)
  • Purpose and vendor preference updates
  • Pluggable storage
  • Pluggable analytics event adapters
  • A lightweight TCF __tcfapi bridge (ping, getTCData, event listeners)

This package intentionally does not include UI or legal policy text.

Install

pnpm add headless-consent

Quick start

import { ConsentEngine, createMemoryStorage, createTcfApiBridge } from "headless-consent";

type PurposeId = "necessary" | "analytics" | "marketing";
type VendorId = "ga" | "meta";

const engine = new ConsentEngine<PurposeId, VendorId>({
  purposes: [{ id: "necessary", required: true }, { id: "analytics" }, { id: "marketing" }],
  vendors: [
    { id: "ga", purposeIds: ["analytics"] },
    { id: "meta", purposeIds: ["marketing"] },
  ],
  storage: createMemoryStorage(),
});

engine.acceptAll();

engine.savePreferences({
  purposes: { analytics: true, marketing: false },
  vendors: { ga: true, meta: false },
});

const tcfApi = createTcfApiBridge(engine);
const tcfTarget: Record<string, unknown> = {};
tcfApi.mount(tcfTarget);

localStorage example (common simple-site setup)

For small sites, browser storage is typically the simplest way to persist consent state.

import { ConsentEngine, createLocalStorageLikeStorage } from "headless-consent";

type PurposeId = "necessary" | "analytics" | "marketing";
type VendorId = "ga" | "meta";

const storage = createLocalStorageLikeStorage(window.localStorage, "headless-consent:v1");

const engine = new ConsentEngine<PurposeId, VendorId>({
  purposes: [{ id: "necessary", required: true }, { id: "analytics" }, { id: "marketing" }],
  vendors: [
    { id: "ga", purposeIds: ["analytics"] },
    { id: "meta", purposeIds: ["marketing"] },
  ],
  storage,
});

Analytics adapter example

import { ConsentEngine, createMemoryStorage, type AnalyticsAdapter } from "headless-consent";

const gtagAdapter: AnalyticsAdapter = {
  track(event, snapshot) {
    globalThis.gtag?.("event", event, {
      consent_status: snapshot.status,
      tc_string: snapshot.tcString,
    });
  },
};

const engine = new ConsentEngine({
  purposes: [{ id: "necessary", required: true }, { id: "analytics" }],
  storage: createMemoryStorage(),
  analytics: [gtagAdapter],
});

Google Analytics integration example

Google Analytics typically uses Consent Mode signals. A common pattern is:

  1. Set consent defaults to denied before GA initialization.
  2. Update consent mode when user preferences change.
  3. Optionally expose __tcfapi for other partners that consume TCF signals.
import { ConsentEngine, createLocalStorageLikeStorage, createTcfApiBridge } from "headless-consent";

type PurposeId = "necessary" | "analytics" | "marketing";
type VendorId = "ga";

type GtagConsent = "granted" | "denied";
type GtagCommand = "default" | "update";
type Gtag = (
  command: "consent",
  action: GtagCommand,
  payload: {
    analytics_storage: GtagConsent;
    ad_storage: GtagConsent;
    ad_user_data: GtagConsent;
    ad_personalization: GtagConsent;
  },
) => void;

const gtag = globalThis.gtag as Gtag | undefined;

const engine = new ConsentEngine<PurposeId, VendorId>({
  purposes: [{ id: "necessary", required: true }, { id: "analytics" }, { id: "marketing" }],
  vendors: [{ id: "ga", purposeIds: ["analytics"] }],
  storage: createLocalStorageLikeStorage(window.localStorage, "headless-consent:v1"),
});

gtag?.("consent", "default", {
  analytics_storage: "denied",
  ad_storage: "denied",
  ad_user_data: "denied",
  ad_personalization: "denied",
});

const applyGoogleConsent = () => {
  const analyticsAllowed = engine.canRun({ vendorId: "ga", purposeIds: ["analytics"] });
  const value: GtagConsent = analyticsAllowed ? "granted" : "denied";

  gtag?.("consent", "update", {
    analytics_storage: value,
    ad_storage: "denied",
    ad_user_data: "denied",
    ad_personalization: "denied",
  });
};

applyGoogleConsent();
engine.subscribe(() => {
  applyGoogleConsent();
});

const tcfApi = createTcfApiBridge(engine);
tcfApi.mount(window as unknown as Record<string, unknown>);

Adjust the mapping from purposes and vendors to Google signals based on your legal basis and policy decisions.

Storage choices: required vs recommended

  • Required for runtime/spec correctness: Persist consent in browser storage (cookie or localStorage) so your runtime can restore state and provide consistent __tcfapi responses.
  • Recommended for stronger compliance evidence: Also persist consent events server-side (for example: timestamp, consent action, policy version, region, and consent payload).

In other words, browser storage is usually enough to satisfy technical runtime behavior, while backend persistence improves auditability and legal defensibility.

Compliance responsibilities for consumers

This library is a technical runtime, not legal advice and not a turnkey CMP product. To run a compliant implementation (GDPR/ePrivacy/TCF or local equivalents), consumers must implement and validate all items below.

1) Legal and policy controls

  • Define legal entities, data controller relationships, and regions where consent applies.
  • Define legal basis per purpose/vendor (consent or other legal basis where allowed).
  • Publish and maintain accurate privacy and cookie policy text.
  • Keep policy version history and map runtime config to policy versions.

2) UX requirements

  • Build first-layer and second-layer UI for banner and preferences.
  • Offer Accept, Reject, and Manage Preferences with equal ease of access.
  • Provide clear purpose and vendor information in plain language.
  • Provide persistent ability to reopen preferences and withdraw consent.
  • Localize content and accessibility (keyboard, focus management, screen readers).

3) Pre-consent enforcement

  • Block all non-essential cookies, SDK initialization, and tracking requests before consent.
  • Ensure tags do not run during hydration, route transitions, or async script races.
  • Gate each integration by purpose/vendor checks via engine.canRun(...).
  • Revoke or disable integrations when consent is withdrawn.

4) Data and storage design

  • Select secure storage strategy (cookie/localStorage/server) per jurisdiction and risk profile.
  • Implement retention and deletion policies for consent records.
  • Handle consent expiration and renewal prompts.
  • Ensure cross-subdomain and cross-device behavior matches your policy commitments.

5) Consent recordkeeping and auditability

  • Persist timestamped consent snapshots and policy version metadata.
  • Record user action source (accept all, reject all, granular save, withdrawal).
  • Store the consent payload needed to defend processing decisions.
  • Provide internal tooling to inspect and export consent records when required.

6) TCF-specific responsibilities (if applicable)

  • Register and maintain correct CMP metadata and IDs in the relevant ecosystem.
  • Keep vendor/purpose data synchronized with current framework requirements.
  • Validate __tcfapi behavior in page and iframe contexts used by partners.
  • Track TCF policy updates and run migration tests for new framework versions.

7) Integration responsibilities

  • Map each analytics/ads SDK to specific purposes/vendors.
  • Verify each provider respects granted/denied states.
  • Validate Google/Meta/other partner-specific consent APIs where needed.
  • Ensure server-side pipelines honor consent state, not just client-side tags.

8) Testing and release controls

  • Add automated tests for default-deny behavior and each consent transition.
  • Run browser matrix tests (including private mode and adblock edge cases).
  • Test SPA navigation and delayed script loading scenarios.
  • Add regression tests that fail if any non-essential cookie is set pre-consent.

9) Ongoing operations

  • Monitor production for unauthorized cookie writes and tracker calls.
  • Re-audit when adding vendors, changing UI copy, or changing tag manager rules.
  • Re-run legal and technical review on regulatory or framework changes.

Suggested implementation checklist

  • Initialize ConsentEngine early, before third-party SDK bootstrap.
  • Render banner/preferences UI from engine.getState() + engine.subscribe(...).
  • Wire UI actions to acceptAll, rejectAll, and savePreferences.
  • Gate every provider call through a dedicated adapter that checks canRun(...).
  • Mount the TCF bridge when partners require __tcfapi.
  • Emit consent events to your observability stack for auditing.

Development

vp install
vp check
vp test
vp pack