JavaScript API
Kernel API
Get a kernel
The kernel holds consent state and runs commands. Every JavaScript setup has one:
consent.kernel, frominit()in@c15t/browser.runtime.kernel, fromcreateConsentRuntimeinc15t/runtime.- A kernel you create yourself, as shown below.
The kernel alone supplies state and commands. It renders nothing, loads no script and reads no storage; the modules around it do that.
This fragment assumes one of the kernels above. Keep unsubscribe and call it
during teardown.
Read state
| Member | Returns | What it does |
|---|---|---|
getSnapshot() | ConsentSnapshot | The current state, frozen. Cheap to call. See the snapshot reference. |
subscribe(listener) | () => void | Call listener with each new snapshot. |
getServerSnapshot() | ConsentSnapshot | The first snapshot, as a server render saw it. Render from this during hydration, then switch to getSnapshot(). |
refresh(now?) | ConsentSnapshot | Evaluate again at now, so an expired choice cannot linger behind a timer. Returns the new snapshot. |
A snapshot keeps the same object for a field whose value did not change, so
previous.effectivePermissions === next.effectivePermissions tells you
permissions did not change.
Choose commands by intent
Commands are asynchronous and are the only way to record anything.
| Command | Returns | Effect |
|---|---|---|
commands.init() | Promise<{ ok, error? }> | Resolve the policy through the transport. Failed attempts retry in the background. |
commands.save('all') | Promise<SaveResult> | Record acceptance of every category in scope. |
commands.save('none') | Promise<SaveResult> | Record rejection of every optional category in scope. |
commands.save({ marketing: false }) | Promise<SaveResult> | Record only the categories you name. |
commands.save() | Promise<SaveResult> | Record the staged draft, falling back to the current choice or the policy's defaults for each category. |
commands.dismissNotice() | Promise<NoticeDismissResult> | Record that the visitor saw a notice. Grants nothing. Only works while promptRequirement.kind is notice. |
commands.identify(user) | Promise<void> | Link the record to { externalId, identityProvider?, identityToken? } and load the server's record for that subject. The token verifies the link. |
save(input, context) takes a second argument. context.categories narrows
'all', 'none' or an empty input to the categories your UI displays, which
is how @c15t/browser limits Accept All to the dialog's categories.
context.vendors records vendor-level grants.
A save updates permissions and notifies subscribers before the backend
request starts. SaveResult.ok reports the request. When it fails, the choice
stays in effect and the payload is queued for retry.
Consent state
documents the ordering.
Change inputs and UI state
set operations run synchronously and notify subscribers. None of them
records a choice.
| Setter | What it changes |
|---|---|
set.activeUI('banner' | 'dialog' | 'none') | The surface your UI should show. |
set.draft(values) | Staged category values that a no-argument save() confirms. Never a grant. |
set.overrides({ country, region, language, gpc }) | Decision inputs, merged into the current ones; undefined clears a key. Call commands.init() after it to resolve the policy again. |
set.language(code) | The language override. |
set.privacySignals({ gpc }) | The detected GPC signal. c15t reads the browser's signal itself; use this to pass one detected elsewhere, such as a Sec-GPC request header. |
set.consentCategories(categories) | The configured categories. Discovered categories stay. |
set.registerConsentCategories(categories) | Add categories your integrations use. They stay for the kernel's lifetime. |
set.vendors(patch, options?) | Merge declared vendors. |
set.subjectId(id) | The subject ID. |
set.iab(patch) | Written by the IAB module. Do not call it yourself. |
Apply stored records
Pass validated records as initialRecords to createConsentKernel(), such
as a choice read from a cookie with readStoredRecordsFromCookieHeader().
They apply without counting as a visitor action. In the browser,
createPersistence()
reads storage into the kernel on mount and keeps tabs in step afterwards.
Events
kernel.events.on(type, listener) returns an unsubscribe function. Each
listener receives the event object.
| Event | Fields | When |
|---|---|---|
init:applied | snapshot | A policy resolved, from the transport or a resolved prefetch. |
init:failed | error, attempt, nextRetryMs | Initialization failed. nextRetryMs is null when no retry remains. |
choice:recorded | snapshot, confirmed, actionAt | An accept, reject or save recorded categories. |
permissions:changed | snapshot, previous | Effective permissions changed value, for any reason. |
notice:dismissed | snapshot, dismissal | A notice was acknowledged. |
vendors:recorded | snapshot, actionAt | A save changed which vendors are denied. |
vendors:set | snapshot | The declared vendor list changed. |
overrides:set | snapshot | Decision inputs changed. |
user:identified | snapshot | identify linked a user. |
subject:resolved | snapshot | A save received the server's subject ID. |
iab:set | snapshot | The IAB state changed. |
records:cleared | none | Stored records were cleared. |
save:replayed | subjectId, ok, rejected? | A queued save was sent again. rejected holds the backend's refusal code. |
command:init:started, command:save:started | none | A command began. |
command:init:completed, command:save:completed | result | A command finished. |
command:error | command, error | A command failed. |
Use choice:recorded to react to a visitor's decision and
permissions:changed to start and stop code. Callbacks
explains the difference.
How listeners are notified
kernel.subscribe() and kernel.events.on() listeners run synchronously when
a change commits, in registration order. Each receives the frozen snapshot that
change produced.
- A listener that throws does not stop the listeners after it or fail the
command that caused the change. In a browser page c15t passes the error to
reportError. Everywhere else, including Bun and Deno servers wherereportErrorwould end the process, it logs the error withconsole.error. - A listener can call commands and setters. Before the nested call returns, every other listener receives the change in progress and then the new one. No listener misses a change, such as a denial that another listener immediately reverses, or receives changes out of order.
- A listener added during a notification first receives the next change. A listener removed during a notification is not called again.
- Listeners that keep changing consent in response to each other are stopped after 100 nested notifications, and the error is reported. Notifications already queued still arrive, so other listeners end on the current snapshot. A listener that changes consent after that point is not called again.
Create a kernel yourself
Most apps should use createConsentRuntime or @c15t/browser, which connect
the modules for you. Create the kernel directly only when you need to choose
every module:
This kernel stores choices and loads scripts. It has no iframe blocker,
network blocker or data clearing, and it does not reload the page when a
visitor withdraws permission, so a vendor that already ran keeps running.
Add each one you need from its c15t/modules/* entry point, and dispose it
before the kernel:
| Module | Entry point | Page |
|---|---|---|
| Persistence | c15t/modules/persistence | Persistence |
| Script loader | c15t/modules/script-loader | Script loader |
| Network blocker | c15t/modules/network-blocker | Network blocker |
| Iframe blocker | c15t/modules/iframe-blocker | Iframe blocker |
| Clear on revocation | c15t/modules/clear-on-revocation | Clear on revocation |
| Reload after withdrawal | watchRevocationReload from c15t | This page, below |
watchRevocationReload({ kernel, isEnabled, getOnBeforeReload? }) reloads the
page after a save turns off something that was allowed. It returns a
disposer.
createConsentKernel(config) takes a transport, not a transport factory.
Use createHostedTransport({ backendURL }) or createOfflineTransport()
from c15t, or your own object with init and save. Other useful config
fields:
| Field | What it does |
|---|---|
transport | Carries init, save, identify and loadSubjectRecord. Without it, commands succeed and send nothing. |
consentCategories | Categories to offer, within the policy's scope. |
initialOverrides, initialUser | Decision inputs and an identified user at start. |
initialRecords, initialPolicyResolution | Records and a policy a server already resolved. |
initialTranslations | Copy to start with. |
translationOverrides | Messages keyed by language, such as your i18n.messages. The kernel applies the entry for the active language over initialTranslations and over each init response, key by key. |
initRetry | Retry for a failed init: { maxAttempts, baseDelayMs, maxDelayMs }, or false. Defaults to 5 attempts from 1 second up to 30 seconds. |
now | The evaluation clock at start, for a server render and its hydration to agree. |
Construction has no side effects, so a server can build a kernel and read its snapshot.
Check it works
- Subscribe to the kernel and log
policyPendingandresolution.status. Aftercommands.init(),policyPendingisfalseand the status ismatched. - Add an
events.on('choice:recorded', …)listener. It logs once for each accept, reject or save, and not on reload. - Call
kernel.dispose(). Background retries stop;getSnapshot()still works.