How it works
What happens under the hood. Useful for debugging, not required for using libconsent.
Nothing on this page is needed to use libconsent. It is here for when something behaves unexpectedly and you want to know why.
The rule
Whether something may run comes down to four steps, applied identically by the gate, the hooks and the preferences dialog:
essential is granted, immediately and without waiting.Step one is deliberately first. Whatever the user was shown and clicked is what happens — a service they turned off does not run because its category is on, because the site marked it essential, or because Consent Mode would have let it load. There is no route around it.
It is also why ambiguity resolves to denied. If a platform reports the same service twice and the answers disagree, the denial wins:
// The user allowed one row and refused the other.
services: { "Google Analytics": true, "GA4": false }
// → useHasConsent("google-analytics") === falseNames are compared with case and punctuation removed, so Google Analytics 4,
google_analytics_4 and googleAnalytics4 are the same key.
One caveat, for `essential` only
Step two runs before the platform has reported, which is the point of
essential — those services are needed for the site to work and don't wait
for an answer. If the platform then reports a denial for one, it stops
rendering, but it will have rendered until that point. Everything in the other
three categories waits, so this only applies to what you marked essential.
Why nothing is granted at first
useConsent().ready is false until the platform answers, and the platform only
answers in the browser. So the server render and the first client render agree:
gated components show their fallback, then swap once consent is known. There is
nothing to suppress and no flash of the wrong thing — as long as you don't read
localStorage yourself to get ahead of it.
State
{
ready, // the platform has loaded and answered
hasDecision, // the user chose, rather than us showing defaults
view, // "hidden" | "banner" | "settings"
categories, // { essential: true, analytics: false, … }
services, // per-service answers, by normalised name
}view is which consent UI libconsent should render. Platforms that draw their
own banner always report "hidden", which is why <ConsentBanner> can stay
mounted while you switch between them.
Adapters
An adapter is one function. It loads the platform's script, subscribes to whatever that platform calls a consent event, reports the answer in the shape above, and returns the actions the platform supports:
{ open, openSettings, close, acceptAll, rejectAll, reset, setConsent }All of them are optional. useConsent().supports reports what a platform can
actually do, and calling something it can't warns in development rather than
throwing in production.
Reports replace categories and services rather than merging them — a
platform sends its whole answer each time, and an override it stops mentioning
has been withdrawn. Identical reports are dropped, so a platform re-announcing
unchanged consent on every click doesn't cause a render.
Writing an adapter is covered in Writing an adapter.
Categories
The four unified categories and what platforms tend to call them:
| libconsent | Cookiebot | OneTrust | Usercentrics | consentmanager |
|---|---|---|---|---|
essential | necessary | C0001 | essential | Necessary |
functional | preferences | C0003 | functional | Functional |
analytics | statistics | C0002 | analytics | Measurement |
marketing | marketing | C0004/C0005 | marketing | Advertising |
A platform with categories of its own keeps them: they appear in
useConsent().categories under their own name, and useHasConsent("social-media")
answers. The four above are the ones you can rely on everywhere.