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

IdShown asConsent Mode signalsCan be refused
necessaryNecessarysecurity_storageNo
functionalityFunctionalfunctionality_storageYes
experiencePreferencespersonalization_storageYes
measurementStatisticsanalytics_storageYes
marketingMarketingad_storage, ad_user_data, ad_personalizationYes

The labels can be changed per site and language; the ids never change.

dataLayer events

EventWhenWith
consent_updateEvery change, and on page load when a choice existsconsent (category → true/false), consent_mode (signal → granted/denied)
consent_marketing_updateMarketing was just allowed (likewise consent_measurement_update, consent_experience_update, consent_functionality_update)—
consent_withdrawnA category that was allowed is now refusedcategories
consent_regionEvery page loadconsent_region: country, region, jurisdiction
consent_recordAfter a choice is storedconsent_id, consent_time
consent_userAfter identify(), once Statistics is alloweduser_id
consent_user_dataAfter identify() with an email, once Marketing is alloweduser_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>

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-category takes 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-cookieconsent markup and window.Cookiebot keep 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