JavaScript API
Subscribe to consent, open the preferences modal, identify users, replay on grant.
Overview
The banner exposes a small global at window.cookielint. Use it to subscribe to consent changes, gate analytics initialisation, or open the preferences modal from a footer link. Categories are uppercase (NECESSARY, PREFERENCES, ANALYTICS, MARKETING); decision values are 'GRANTED' or 'DENIED'.
consent.onChange
// Subscribe to consent changes. The listener fires once with the
// current state on subscribe (if any), then on every decision.
window.cookielint.consent.onChange((state) => {
const analytics = state.decisions.ANALYTICS;
if (analytics === 'GRANTED') {
initAnalytics();
} else {
stopAnalytics();
}
});
// Open the preferences modal from a custom button.
document.getElementById('open-cookies')?.addEventListener('click', () => {
window.cookielint.consent.open();
});consent.waitFor
Use consent.waitFor(category) when you want a Promise instead of a subscription. It resolves the first time the category is granted and rejects the first time it is denied:
// Top-level await pattern: only initialise analytics once the
// visitor has explicitly granted ANALYTICS. If they reject,
// the catch fires and we leave the SDK uninitialised.
try {
await window.cookielint.consent.waitFor('ANALYTICS');
initAnalytics();
} catch {
/* visitor denied; skip init */
}Dry run
During integration, flip dry-run on. The blocking engine still classifies every request, cookie, and script but lets them through and logs to the console with the vendor and category it would have caught. Flip off when you ship:
// Paste into the browser console on your staging site.
window.cookielint.dryRun(true);
// Browse around, watch the console for lines like:
// [CookieLint dry-run] would block network (ANALYTICS) { url: ... }
// [CookieLint dry-run] would block cookie (MARKETING) { name: ... }
// Turn off when done:
window.cookielint.dryRun(false);consent.identify
For logged-in apps, consent.identifyties a visitor's decisions to their account so the banner does not reappear when they switch device or clear cookies. The function is a no-op until you flip the workspace toggle in Settings > Identified visitor preferences. The user-id is hashed locally before transit; the raw id never leaves the visitor's browser.
// Call after auth resolves. First time, pass their stored
// preferences; the banner persists them server-side.
await window.cookielint.consent.identify(user.id, {
ANALYTICS: 'GRANTED',
MARKETING: 'DENIED',
});
// Later sessions: pass only the userId.
// If we have stored prefs, the banner skips itself.
await window.cookielint.consent.identify(user.id);Cross-tab sync
Cross-tab sync needs no code. When the visitor accepts in one tab, every other tab on the same origin receives the same decision through BroadcastChannel. The banner closes itself in the other tabs and subscribers fire as if the visitor decided there. The example below shows what a subscriber sees in the receiving tab:
window.cookielint.consent.onChange((state) => {
if (state.source === 'user') {
// May be the visitor in THIS tab, or one in another
// tab whose decision just arrived via BroadcastChannel.
refreshDashboard(state.grantedCategories);
}
});API reference
consent.getState() | Returns the persisted state synchronously: { siteId, bannerConfigVersion, decisions, grantedCategories, capturedAt, locale, source, consentId }. Returns null before the visitor decides. |
consent.onChange(fn) | Subscribes to consent changes. Fires once on subscribe with the current state, then on every decision. Returns an unsubscribe function. |
consent.open() | Reopens the banner with the preferences view. |
consent.accept(category) | Programmatically grants a category. Persists, posts a fresh receipt, re-emits all signals. |
consent.reject(category) | Programmatically denies a category. Required categories cannot be denied. |
banner.hide() | Hides the banner without recording a decision; reappears on next visit. |
consent.waitFor(category) | Returns a Promise that resolves when the category is GRANTED and rejects when it is DENIED. If the stored state already covers the category, resolves or rejects on the next microtask. |
cookielint.dryRun(true) | Developer tool. When on, the blocking engine LOGS what it would block to the console but lets the request through. Lets you audit your site live without breaking analytics during the review. Toggle off when done. |
consent.identify(userId, prefs?) | Tie a logged-in visitor's consent decisions to their account. Provide preferences on first call to persist server-side; call again on later sessions with just the userId and the banner skips itself and applies the stored choices. Off by default per workspace; enable in Settings > Identified visitor preferences first. |
Each decision also fires a DOM event. Listen for cookielint:decision on window if you prefer events to subscribers.
Replay on consent
When the visitor grants a category that was previously denied, the engine replays the things it had held back, on the same page view, without a reload:
Scripts | Tags that were rewritten to an inert data URL are re-inserted with their original src so the browser fetches and executes them. SDKs that buffer pre-load calls into dataLayer or their own queue (GA4, GTM, Meta Pixel, Segment) drain those calls automatically once the loader runs. |
Images | Tracking pixels whose src was swapped to a 1x1 transparent placeholder have their real URL restored, which triggers the browser to fetch them. |
Iframes | Frames pointed at about:blank get their real src restored. Frames are navigated and an onload event fires. |
Cannot be replayed
Three things cannot be replayed: HTTP requests already responded to with 204, cookies that were dropped at write time, and localStorage writes that were dropped. The customer-side JS that originated those calls already returned, so the engine has nothing to re-trigger. SDKs that re-fire on the next tick (most analytics) recover anyway via their dataLayer/queue; SDKs that fire once and forget will only catch up on the next page navigation.

