For developers
The names below are stable: categories, dataLayer events, the JavaScript API and the markup for code that must wait. We only ever add to them.
Categories
| Id | Shown as | Consent Mode signals | Can be refused |
|---|---|---|---|
necessary | Necessary | security_storage | No |
functionality | Functional | functionality_storage | Yes |
experience | Preferences | personalization_storage | Yes |
measurement | Statistics | analytics_storage | Yes |
marketing | Marketing | ad_storage, ad_user_data, ad_personalization | Yes |
The labels can be changed per site and language; the ids never change.
dataLayer events
| Event | When | With |
|---|---|---|
consent_update | Every change, and on page load when a choice exists | consent (category → true/false), consent_mode (signal → granted/denied) |
consent_marketing_update | Marketing was just allowed (likewise consent_measurement_update, consent_experience_update, consent_functionality_update) | — |
consent_withdrawn | A category that was allowed is now refused | categories |
consent_region | Every page load | consent_region: country, region, jurisdiction |
consent_record | After a choice is stored | consent_id, consent_time |
consent_user | After identify(), once Statistics is allowed | user_id |
consent_user_data | After identify() with an email, once Marketing is allowed | user_data.sha256_email_address |
Per-category events fire only when a category is newly allowed during the page view, never on page load, so a tag triggered by one never fires twice. Refusals arrive on consent_update.
JavaScript API
window.TrackableConsent = {
ready, // a Promise: resolves once the visitor's state is known
get(), // { categories, jurisdiction, chosen, consentId?, time? }
has(category), // true once that category is allowed
on('change', fn), // fn runs on every change; returns a function that stops it
open(view), // 'preferences' (default) or 'banner'
acceptAll(), rejectAll(), // for your own buttons; recorded like the banner's
set({ measurement: true, marketing: false }),
holdUntil(category, fn), // run fn once the category is allowed (now, if it already is)
identify(id, { email }), // the signed-in customer, to tags only with consent; identify(null) on sign-out
};Calls made before the banner has loaded are queued, so they are safe anywhere on the page:
<script>
(window.TrackableConsent = window.TrackableConsent || []).push(function (tc) {
tc.holdUntil('marketing', function () { /* load the chat widget */ });
});
</script>Links in your footer
| Link | Does |
|---|---|
<a href="#trackable-preferences">Cookie settings</a> | Opens the preferences window, without leaving the page |
<a href="#trackable-privacy-choices">Your Privacy Choices</a> | US visitors: opts out of sale and sharing in one click, then shows the preferences. Elsewhere it opens the preferences |
Code that must wait
Mark scripts and embeds with the category they need. They run, in page order, once every category listed is allowed:
<!-- An external script: runs once Marketing is allowed -->
<script type="text/plain" data-tc-category="marketing" src="https://example.com/pixel.js"></script>
<!-- Inline code: runs once Statistics is allowed -->
<script type="text/plain" data-tc-category="measurement">
console.log('Statistics allowed');
</script>
<!-- An embed: a placeholder with an Allow button until Marketing is allowed -->
<iframe data-tc-category="marketing" data-tc-src="https://www.youtube-nocookie.com/embed/VIDEO_ID"></iframe>data-tc-categorytakes one or more categories, separated by spaces or commas. Cookiebot's names (preferences,statistics) work too; an unknown name counts as Marketing.data-tc-type="module"gives the released script that type.- If a visitor withdraws consent, the page reloads, so nothing keeps running.
- Automatic blocking (Tracking → Blocking and tags) holds well-known trackers pasted into the page without any markup. The markup above is the guarantee.
- Moving from Cookiebot: switch on Cookiebot compatibility there, and
data-cookieconsentmarkup andwindow.Cookiebotkeep working.
Signed-in customers
identify() tells the banner who is signed in. It is kept in memory only, and reaches the dataLayer only with consent: the ID once Statistics is allowed, the email only hashed, once Marketing is.
<script>
(window.TrackableConsent = window.TrackableConsent || []).push(['identify', 'CUSTOMER_ID', { email: 'jane@example.com' }]);
</script>Content Security Policy
Allow https://cmp.trackable.digital in script-src and connect-src. With a nonce, give the banner script the page's nonce attribute: the scripts it adds carry it too.
The banner's language follows the page's <html lang>, then the visitor's browser. More than 35 languages are built in. See also Google Tag Manager.
Next: Styling the banner