Skip to main content

HTML Scripts and embeds

Vendor consent

A visitor allows marketing, then switches off X Pixel. Other marketing scripts load; X Pixel does not. c15t loads a script, network request or iframe that names a vendor only when both are true:

  • Its category is allowed.
  • The visitor has not switched its vendor off.

A vendor switch never grants a category. With marketing denied, X Pixel stays blocked whatever its own switch says. You do not need IAB TCF for this. Under an iab policy, c15t ignores vendor slugs and takes vendor consent from the TC string instead; see IAB TCF.

Declare the vendors

Queue a config call with vendors before the c15t script tag. Each id is the slug your gated tags and iframes name:

index.html
<!-- Runs before the deferred c15t.js tag, so init() picks it up -->
<script>
	window.c15t = window.c15t || [];
	c15t.push([
		'config',
		{
			vendors: [
				{
					id: 'posthog',
					name: 'PostHog',
					category: 'measurement',
					description: 'Product analytics and session insights.',
					privacyPolicyUrl: 'https://posthog.com/privacy',
				},
				{
					id: 'youtube',
					name: 'YouTube',
					category: 'measurement',
					description: 'Embedded videos.',
					privacyPolicyUrl: 'https://policies.google.com/privacy',
				},
				{
					id: 'x-pixel',
					name: 'X Pixel',
					category: 'marketing',
					description: 'Ad conversion tracking.',
					privacyPolicyUrl: 'https://x.com/en/privacy',
				},
			],
		},
	]);
</script>

vendors is an ordinary config option, so c15t.init({ vendors }) on a page with data-manual works too. See configuration.

Vendor fields

FieldRequiredBehavior
idYesLowercase slug of up to 64 characters: letters, digits, ., _ and -.
nameYesName shown in the preference dialog.
categoryYesA category, or a condition such as { or: ['measurement', 'marketing'] }.
privacyPolicyUrlYesLink shown next to the vendor.
description, legalName, homepageUrlNoExtra detail shown on the vendor card.
disabledNoList the vendor without a switch. A stored denial for it no longer applies.

The id must match the vendor slug on the script. Every @c15t/integrations helper sets vendor to its script ID, so xPixel() is x-pixel, gtag() is gtag and cloudflareZaraz() is cloudflare-zaraz. Each vendor guide names its slug.

You don't need to declare a helper's vendor. Each helper also sets vendorDetails on its script: the vendor's name, privacy policy, homepage and legal entity. The dialog lists the vendor with its own switch from those. Declare the vendor in vendors to replace them, for example to link your own data processing notice, or when you self-host a tool such as Matomo, Umami, Plausible or PostHog and the vendor's privacy policy doesn't cover your install. A declaration replaces vendorDetails as a whole and doesn't merge with it.

Your own scripts can set vendorDetails too. A script with a vendor slug and no name and privacyPolicyUrl from any source still loads with its category, but the dialog has no switch for it. In development, c15t logs a console warning that names the slug. Production builds skip the warning.

A self-hosted backend can declare vendors too. /init returns them and c15t merges them with the vendors in code. When both declare the same id, the code declaration wins.

Gate tags and iframes by vendor

Add data-c15t-vendor to a gated <script type="text/plain"> tag, next to its data-c15t-category. The tag runs once its category is allowed and the visitor has not switched its vendor off:

index.html
<!-- PostHog's snippet, saved as a file on your site -->
<script
	type="text/plain"
	data-c15t-category="measurement"
	data-c15t-vendor="posthog"
	src="/vendor/posthog.js"
></script>
<!-- X Pixel's base code, pasted inline -->
<script
	type="text/plain"
	data-c15t-category="marketing"
	data-c15t-vendor="x-pixel"
>
	!function(e,t,n,s,u,a){e.twq||(s=e.twq=function(){s.exe?s.exe.apply(s,arguments):s.queue.push(arguments);
	},s.version='1.1',s.queue=[],u=t.createElement(n),u.async=!0,u.src='https://static.ads-twitter.com/uwt.js',
	a=t.getElementsByTagName(n)[0],a.parentNode.insertBefore(u,a))}(window,document,'script');
	twq('config','YOUR_X_PIXEL_ID');
</script>

Add data-vendor to a gated iframe:

index.html
<iframe
	data-src="https://www.youtube-nocookie.com/embed/czTksCF6X8Y?playsinline=1"
	data-category="measurement"
	data-vendor="youtube"
	title="YouTube video"
	allow="encrypted-media; picture-in-picture"
	allowfullscreen
></iframe>
TargetField
Gated <script type="text/plain"> tagdata-c15t-vendor="x-pixel" next to data-c15t-category
Gated iframedata-vendor="x-pixel"
scripts entry in configvendor: 'x-pixel'
Network blocker rulevendor: 'x-pixel'

data-c15t-vendor does nothing without data-c15t-category. On a page whose c15t tag has a nonce, a vendor-gated tag still needs that nonce, like every gated tag. An iframe with data-vendor and no data-category is gated on the vendor alone.

A tag's data-c15t-vendor slug counts as declared, so a stored denial holds even before your vendors list or the backend names that vendor. The preference dialog lists the vendor only once a declaration gives it a name and a privacyPolicyUrl.

What the preference dialog shows

The preference dialog in c15t.js lists each declared vendor under its category, with its own switch. c15t.headless.js has no dialog; record vendor switches from your own UI with save().

The dialog lists only vendors that have a name and a privacyPolicyUrl. A vendor's switch is disabled while its category is off. Save records the vendors the visitor changed. Accept All and Reject All clear every vendor denial, so each vendor follows its category again. The labels come from consentManagerDialog.vendors in the translations: title, privacyPolicy, disabledByCategory and switchLabel.

Read and record vendor choices from your own script

window.c15t and c15t.client have three reads for vendor consent:

MethodReturns
getDeclaredVendors()The vendors from vendors, the backend and the slugs on scripts, gated tags and iframes. Empty under an IAB policy.
getVendorChoice()The recorded vendor decision, whose denied lists the vendors the visitor switched off, or null before any vendor decision.
isVendorAllowed(id)true when the vendor is declared, its category is allowed and the visitor has not switched it off.

Reads cannot be queued, so call them from onInit or after c15t.ready().

A vendor reads as allowed only when it is declared, its category is allowed and the visitor has not switched it off. An id that nothing declares, in vendors, on a script or iframe, or from the backend, reads as not allowed. A typo such as x-pixle or a vendor you forgot to declare never looks like consent. In development, c15t logs a console warning that names the id.

save() accepts a vendors map next to the categories. Vendors you leave out keep their recorded state. save can be queued, so this works before and after the tag loads:

<script>
  window.c15t = window.c15t || [];
  c15t.push(['save', { marketing: true, vendors: { 'x-pixel': false } }]);
</script>

Call it from a visitor's action, such as a switch in your own form, never at page load. A save that changes only vendors still fires the consent event, and c15t checks gated tags again, so a tag waiting on a vendor that was switched back on runs. See window.c15t API and events and callbacks.

What c15t stores

c15t stores only the vendors a visitor switched off, and sends the full vendor map to the backend with the consent record. Under an opt-out policy, every vendor starts on. Turning a vendor off that was on reloads the page, the same as withdrawing a category, so code the vendor already ran stops.

What switching a vendor off does not do

  • It does not delete cookies the vendor already set. Clear on revocation does not run, because the category stays allowed. Delete the vendor's cookies from the script's onConsentChange when info.vendor.granted is false.
  • It does not expire. The switch stays off until the visitor changes it or uses Accept All or Reject All, even across a policy change.
  • Adding a vendor does not ask returning visitors again. A new vendor starts on inside an allowed category. Change the policy's copyRevision if a new vendor should prompt again.

Open the page in a private window with DevTools Network open and filtered to ads-twitter.com:

  1. Declare x-pixel and gate X Pixel's tag with data-c15t-category="marketing" and data-c15t-vendor="x-pixel". Open Privacy settings. The marketing row lists X Pixel with a switch.
  2. Allow marketing and save. uwt.js loads.
  3. Open Privacy settings, switch X Pixel off and save. c15t reloads the page. After the reload there is no uwt.js request, and other marketing tags still run.
  4. Reload again. The X Pixel switch is still off, and in the console c15t.isVendorAllowed('x-pixel') returns false.
  5. Turn marketing off. The X Pixel switch is disabled.
  6. Open Privacy settings and click Accept All. uwt.js loads again.