Skip to main content

HTML Advanced

IAB TCF

Load the IAB build

Load c15t.iab.js instead of c15t.js when your policy uses IAB TCF. It contains the CMP, the TC string encoder, the IAB banner and the purpose and vendor preference dialog. c15t.js, c15t.offline.js and c15t.headless.js leave all of that out, so pages without an IAB policy stay smaller. The stock banner on those builds stands aside for a policy that uses the iab model, so a page that resolves one emits an error event and throws an IABUnavailableError (code C15T_IAB_UNAVAILABLE) as an uncaught error. So does c15t.iab.js with iab: false. Load c15t.iab.js for visitors whose policy uses IAB TCF, or remove the iab model from the policy.

<script>
  window.c15t = window.c15t || [];
  c15t.push(['config', {
    iab: { cmpId: YOUR_CMP_ID, vendors: [1, 2, 755] },
    legalLinks: {
      privacyPolicy: { href: '/privacy' },
      cookiePolicy: { href: '/cookies' },
    },
    ui: {
      banner: { legalLinks: ['privacyPolicy', 'cookiePolicy'] },
      dialog: { legalLinks: ['privacyPolicy'] },
    },
  }]);
</script>
<script
  src="https://your-project.inth.app/c15t.iab.js"
  defer
></script>

Replace YOUR_CMP_ID with the ID IAB Europe assigned your CMP at registration. Every TC string names the CMP that wrote it, so never copy an ID from an example. A backend can supply cmpId and the vendor list instead. vendors limits the list to the vendors your site works with.

Load only one c15t bundle. c15t.iab.js throws an error if c15t.js, c15t.offline.js or c15t.headless.js loaded first. When the resolved policy is not an IAB policy, the IAB build shows the ordinary banner. If an IAB policy cannot load its vendor list, the banner shows an error and keeps its confirm buttons disabled.

A self-hosted backend serves this build at /c15t.iab.js. Set script.config.iab on the backend to supply CMP defaults.

Confirm IAB choices from your code

c15t.acceptAll() and c15t.rejectAll() confirm through the CMP when the policy is IAB. For individual choices, change the draft on the CMP handle and confirm it with c15t.saveIAB():

<script>
  window.c15t = window.c15t || [];
  c15t.push(['onInit', async (client) => {
    await client.ready();
    const cmp = client.runtime.iab;
    if (!cmp) return;
    await cmp.whenReady?.();
    cmp.setPurposeConsent(1, true);
    cmp.setVendorConsent(755, true);
    const result = await client.saveIAB();
    if (!result.ok) client.openDialog();
  }]);
</script>

ready() waits for the policy. cmp.whenReady() waits for the vendor list and CMP setup, and rejects if setup fails. The handle also has setPurposeLegitimateInterest, setVendorLegitimateInterest and setSpecialFeatureOptIn. Each edits the draft; saveIAB() confirms it. c15t.save({ ... }) with categories fails under an IAB policy, because it cannot produce a TC string.

The CMP installs window.__tcfapi. c15t.dispose() removes it.

How the stock dialog groups choices

In the stock preference dialog, turning on a purpose also gives consent to the vendors that use that purpose on a consent basis. A purpose's legitimate-interest control changes the purpose and its legitimate-interest vendors, and turning it off records an objection. A stack's control covers its purposes and their consent-based vendors, and shows a mixed state when only some are on. The vendor tab has search, per-vendor consent and legitimate-interest controls, and each vendor's disclosures from the Global Vendor List.

The runtime.iab setters each change only the choice they name. A custom UI that wants the same grouping must set the related vendor choices itself.

How the preference centre shows features

The stock IAB preference centres in React, Next.js, Vue, Svelte, Astro and the script tag follow TCF 2.4 and Policies v5.0.b. Special purposes stay in their locked section with a lock icon, because the visitor cannot object to them.

Features get their own section after the special purposes. It shows the IAB standard text for features from the Global Vendor List's standardTexts.features. If the list has no standard text, it shows the iab.preferenceCenter.features.description translation. Each feature lists its name, description, illustrations and the vendors that use it. The section has no switch, lock or vendor toggle, because TCF forbids showing features next to a control that cannot be disabled.

The feature section follows the theme's spacing and typography tokens. Its disclosure arrows are decorative and stay out of the accessibility tree.

A custom preference UI gets the same rows from resolveIABDialogDisplayModel in @c15t/iab/headless. essentialRows holds the special purposes only. featureRows holds the features, with locked: false and toggle: 'none', so render them without a control. featuresStandardText is the standard text, or null when you should use the translation.

Each vendor's privacy policy and legitimate interest links come from its urls[] entry in the Global Vendor List. The stock preference centres pick the entry for the language the banner shows, then English. A custom UI gets the same links from resolveIABVendorUrls(vendor, language) in @c15t/iab/headless, or from policyUrl and legitimateInterestUrl on the vendors processGVLForDialog returns when you pass it language. Vendor lists older than GVL v3 have a single policyUrl field, which c15t still reads.

c15t stores TCF choices per site and browser, which the TCF calls service-specific scope. It does not sync TC strings across devices, and every TC string it writes has IsServiceSpecific set to 1.

Do not use /consent/check or your own account data to skip the TCF banner on another device. That turns the choice into multi-device scope, which the TCF requires you to disclose in the first layer, and c15t does not show that disclosure. The isServiceSpecific option is deprecated. c15t ignores it and logs a warning when you pass false.

Style and word the IAB UI

The IAB UI takes the same ui.theme, ui.css and ui.shadow options as the ordinary banner; see customize. With shadow: false and your own stylesheet, load both dist/c15t.css and dist/c15t.iab.css from the package.

The IAB banner always shows the c15t branding. ui.dialog.hideBranding hides it in the preference dialog. ui.banner.title, description and the button text options apply to the IAB banner too, and a description can include {partnerCount}. ui.iab.loadErrorText, saveErrorText and moreVendorsText change the loading error, save error and pagination copy.

Set ui: false to render your own IAB UI against the client. The page still downloads the IAB build.

Try it locally

The repository's internals/fixtures/script-tag/iab.html runs the IAB build offline with a sample vendor list. Build @c15t/browser, run bun run --cwd internals/fixtures/script-tag dev and open /iab. It uses a demonstration CMP ID; replace it with your own for a real site.