Next.js Consent API
Callbacks
Put callbacks in c15t.config.ts
Callbacks are functions, and a Server Component cannot pass functions to a
Client Component. React serializes every prop a layout passes across that
boundary. c15t.config.ts is bundled into the browser, so put the callbacks
there instead, under options. Define them in a module:
Then pass them in the config:
ConsentRoot reads the config in the App Router layout and in the
Pages Router _app.tsx alike, so the
same file covers both routers. Keep your existing scripts and other options
in the same call.
ConsentRoot subscribes the callbacks after it mounts in the browser. Server
rendering never calls them. The provider calls the latest callbacks object
it received, so a new object on a later render takes effect without a
remount.
Available callbacks
| Callback | Runs when | Receives |
|---|---|---|
onChoiceRecorded | The visitor clicks Accept All, Reject All or Save Settings, in the stock UI or through useSaveConsents(). | snapshot, the state after the choice; confirmed, the categories this action recorded; actionAt, the time as a millisecond timestamp |
onPermissionsChanged | Any effective permission changes value. | snapshot, the state after the change; previous, the permissions before it |
onError | A consent command fails in the browser, such as a failed /init request or a save the backend rejects. | error, a message string |
onBeforeConsentRevocationReload | A save withdrew a granted category or vendor, right before c15t reloads the page. | preferences, the permissions after the save |
The type for the whole object is ConsentProviderCallbacks from c15t/next.
Callbacks run after c15t updates its state, so snapshot and hooks such as
useConsent() already hold the new values.
onChoiceRecorded or onPermissionsChanged
onChoiceRecorded runs only for a visitor's action. onPermissionsChanged
runs whenever effective permissions change, whatever the cause:
| Event | onChoiceRecorded | onPermissionsChanged |
|---|---|---|
| Visitor accepts, rejects or saves | Yes | Yes, if a permission changed |
| A policy resolves in the browser and allows categories before any choice | No | Yes |
| Global Privacy Control turns off a grant | No | Yes |
| A recorded choice expires | No | Yes |
| Visitor dismisses a notice | No | No |
Use onChoiceRecorded for consent analytics and audit trails, because it
fires only when the visitor chose. Use onPermissionsChanged to start or stop
your own code. Never treat it as proof that the visitor agreed to anything,
and never save consent from it.
How consent works
explains the difference.
Dismissing a notice records no choice and changes no permission. Read it with
useNoticeDismissal() instead. For consent inside one component, use the
hooks, which re-render the component when
consent changes.
Report server-side failures
The provider's onError runs in the browser. resolveConsent has its own
onError option for the server. It runs when the manifest or /init request
fails or runs out of timeoutMs during rendering. The page still renders with
the request-only state and the browser resolves consent after hydration.
Without the option, resolveConsent logs a warning outside production.
This option is fine in the layout, because it runs on the server and never
crosses to the browser. reportToMonitoring stands for your own error
reporter.
Before a revocation reload
Removing a script tag cannot stop code that already ran, so c15t reloads the
page after an accept, reject or save turns off a category or vendor that was
granted. The reload waits for the save request to finish. Right before it,
onBeforeConsentRevocationReload runs synchronously. Use it to call a
vendor's shutdown API or flush queued events. Keep it short, because the
reload does not wait for promises.
A choice that expires, a new policy and a privacy signal change permissions without a visitor action, so they do not reload the page.
To handle revocation yourself, set reloadOnConsentRevoked: false in
options on ConsentRoot. Code the visitor turned off then keeps running
until the next full page load, and onBeforeConsentRevocationReload never
runs.
Verify
- Add the callbacks from this page, open the console and DevTools Network, clear site data and reload under a policy that shows a banner.
- Click Accept All.
onChoiceRecordedandonPermissionsChangedboth log, and the save request to your backend appears in Network. - Reload.
onChoiceRecordeddoes not log again. - Open Privacy settings, turn off a category you allowed and save.
onBeforeConsentRevocationReloadlogs, then the page reloads. After the reload, that category's vendor requests are absent from Network.