libconsent
Adapters

libconsent (built-in)

Consent kept in the browser, banner drawn by your own components.

The default: no external script, consent kept in the browser, and the UI rendered by your own components. It is the only adapter that supports per-service switches.

import { ConsentProvider } from "@libconsent/core";

<ConsentProvider>{children}</ConsentProvider>;

Configure it by passing it explicitly:

import { local } from "@libconsent/core";

const adapter = local({ revision: "2026-01", reloadOnRevoke: true });

<ConsentProvider adapter={adapter}>{children}</ConsentProvider>;

Build the adapter outside your component or in a useMemo. A new adapter object means a new connection.

Options

OptionDefault
storageKey"libconsent"Where the record is kept.
revision—Bump it and everyone who consented under a different value is asked again.
categoriesall fourWhich categories to offer.
storagelocalStorageAdapterSee below.
reloadOnRevokefalseReload after consent is withdrawn, so scripts that already ran actually stop.

What gets stored

{
  "version": 1,
  "updatedAt": "2026-01-14T09:12:33.000Z",
  "revision": "2026-01",
  "categories": { "essential": true, "analytics": true, "marketing": false },
  "services": { "hotjar": false }
}

updatedAt records when the decision was made, which you generally need to be able to show. Anything that doesn't parse, or was written under a different revision or schema version, counts as no consent.

localStorage isn't visible to your server. For middleware, RSCs or anything else that needs the same decision:

import { local, cookieStorage } from "@libconsent/core";

const adapter = local({ storage: cookieStorage() });

cookieStorage({ maxAge, path }) writes a SameSite=Lax cookie holding the same JSON.

Your own storage

Three methods and a subscription:

import type { ConsentStorage } from "@libconsent/core";

const sessionStorageAdapter: ConsentStorage = {
  read: (key) => sessionStorage.getItem(key),
  write: (key, value) => sessionStorage.setItem(key, value),
  clear: (key) => sessionStorage.removeItem(key),
};

subscribe(key, onChange) is optional; it is what keeps other tabs in sync.

Cross-tab behaviour

Consent changed in another tab applies immediately: the banner closes, gated components appear, and an open preferences dialog resets its draft instead of saving over the newer decision.

On this page