JavaScript Modules
Persistence
Where choices are stored
The persistence module writes each recorded choice to a first-party cookie
and to localStorage, and reads them back when the page loads. The visitor's
choice survives reloads and later visits without a backend request.
@c15t/browser and createConsentRuntime run it by default.
| Stored item | Name, with the default key |
|---|---|
| Category choice | c15t cookie and localStorage key |
| Notice acknowledgement | c15t-notice |
| Vendor choice | c15t-vendors |
| Saves waiting to reach the backend | a separate localStorage queue |
The cookie is readable by your server, so a server-rendered page can see the visitor's choice before any script runs.
When records are read and written
Stored records are read synchronously when the module starts, so a returning visitor's choice applies before the first banner renders.
The code that writes and reconciles records is a separate chunk. Once a
banner or dialog has been shown, it starts loading in browser idle time after
the page's load event. It also starts at the first write or reconciliation
if one comes sooner. A returning visitor who sees no prompt downloads it only
when something needs it. Until it has loaded:
- A save waits for its record to be stored before its backend request leaves, and before the save resolves. On the first save of a page load, this can add the time it takes to fetch the chunk.
- If the chunk fails to load, the save keeps waiting rather than count as stored. Its request does not leave, and a revocation reload does not run while storage still holds the old choice. c15t tries the chunk again after 1, 4 and 16 seconds, and at every later save, focus or tab change.
reconcile()returnsfalseand runs when the chunk lands.
clear() needs no chunk. It removes every record from the cookie and
localStorage, stores the clear epoch and resets the kernel's records before
it returns, so reloading right after a clear cannot restore a cleared choice.
pagehidecannot store anything. A page restored from the back/forward cache stores its pending records when the chunk lands. A page that unloads first loses them, along with the save request still waiting for them.
After the chunk has loaded, each write runs in the macrotask after the
visitor acts, and pagehide runs any write still waiting.
Name, domain and lifetime
storageConfig sets where and how long records live:
| Option | Default | What it does |
|---|---|---|
storageKey | 'c15t' | The cookie and localStorage name. Other records use it as a prefix. |
crossSubdomain | false | Set the cookie on the root domain, so www.example.com and shop.example.com share it. |
defaultDomain | current host | An explicit cookie domain, such as '.example.com'. Wins over crossSubdomain. |
defaultExpiryDays | 365 | Cookie lifetime in days. The policy's own validity still decides when a choice expires. |
Use the same storageConfig on every site that shares the cookie. Pass it to
init() or createConsentRuntime, or inside persistence for a runtime:
Keep open tabs in step
Tabs on the same origin share localStorage, so a choice made in one tab
reaches the others at once through the storage event. A tab on another
subdomain that shares only the cookie gets no such event. It reads stored
records again when the page becomes visible or the window regains focus.
Call runtime.reconcileStorage() to read them at another moment, for
example after a response set the consent cookie. With @c15t/browser, call
client.runtime.reconcileStorage(). persistence: { sync: false } on the
runtime or the @c15t/browser client turns the automatic reads off.
Keep open tabs in step
explains the merge rules.
Turn it off
persistence: false on createConsentRuntime or on init() from
@c15t/browser keeps choices in memory only.
Every page load starts without a choice, so the banner shows on every page.
Attach it to your own kernel
A kernel from createConsentKernel stores nothing. Create the module before
commands.init() so stored choices apply first:
createPersistence({ kernel, storageConfig?, skipHydration?, sync? })
returns:
| Member | What it does |
|---|---|
hydrate() | Read storage again. Returns whether any record was found. |
reconcile() | Merge records another tab or runtime stored. Returns whether anything changed. |
clear() | Delete every c15t record and reset the kernel's records. |
dispose() | Stop writing and remove the tab listeners. Stored records stay. |
skipHydration: true skips the first read, for a kernel a server already
seeded with the visitor's records.
Check it works
- Accept all and open DevTools, Application. A
c15tcookie and ac15tlocalStorage entry exist. - Reload. The banner stays closed.
- Open a second tab, reject all there and switch back. The first tab now shows the rejection.