Skip to main content

JavaScript Scripts and embeds

Scripts

Register scripts where you start c15t

Pass the scripts list to whichever API starts c15t. The list is the same in each case, built from @c15t/integrations helpers or your own script objects:

SetupWhere scripts goes
@c15t/browserinit({ scripts, … }), as in the quickstart.
c15t/runtimecreateConsentRuntime({ mode, scripts }), as in headless.
Your own kernelcreateScriptLoader({ kernel, scripts }). See script loader.

The quickstart's src/main.ts shows PostHog:

src/main.ts
import { init, manifest } from '@c15t/browser';
import { posthog } from '@c15t/integrations/posthog';

init({
	mode: manifest(),
	scripts: [
		posthog({
			id: 'phc_your_project_key',
			initOptions: { cookieless_mode: 'never' },
			loadMode: 'after-consent',
		}),
	],
});

Each integration guide gives the helper and options for one vendor. For a vendor without a helper, write the object yourself:

const scripts = [
	{
		id: 'analytics',
		src: 'https://analytics.example/sdk.js',
		category: 'measurement',
		onLoad: () => window.analytics?.track('page_view'),
	},
];

window.analytics stands for your vendor's global. The script loader page lists every field and callback.

When the script loader downloads

With @c15t/browser from npm, the script loader and the network blocker share a separate chunk. It loads when c15t starts, and only if scripts is not empty or networkBlocker has rules, so a page with neither never downloads it. On a page with either, the browser requests it after your JavaScript has run, which delays a returning visitor's consented scripts and held requests by one request.

Your bundler names that chunk, so c15t cannot link it from your HTML. To start the download earlier, add a <link rel="modulepreload"> for the chunk your build emits for @c15t/core/dist/modules/loader-and-blocker.js. With Vite, .vite/manifest.json lists it under that path when build.manifest is on. Give the link fetchpriority="low". c15t needs the chunk only when it starts, and at the default priority the preload can delay your app's own chunks over HTTP/1.1. The script-tag build, c15t.js, already contains the loader.

Gate a snippet in your HTML

@c15t/browser also runs <script type="text/plain" data-c15t-category> tags in your index.html once their category is allowed, in page order. Use it for a vendor snippet you would rather keep in HTML. createConsentRuntime and a plain kernel do not scan the page for these tags; activateGatedScripts(snapshot) from @c15t/browser runs them for you.

With the nonce option set, @c15t/browser runs only the tags that carry the same nonce, and marks the others data-c15t-activated="untrusted". Pass { nonce } as the third argument of activateGatedScripts for the same check. See Content Security Policy.

When a visitor withdraws permission

A script that has run cannot be unloaded. @c15t/browser and createConsentRuntime reload the page after a visitor turns off a category they had allowed, so the new page starts without that vendor. Allowing a category never reloads.

Set reloadOnConsentRevoked: false to handle withdrawal yourself, for example with the vendor's opt-out call in the script's onConsentChange. Until the next page load, code that already ran keeps running. callbacks.onBeforeConsentRevocationReload runs right before the reload, for work that must finish first. A kernel you create yourself does not reload; add watchRevocationReload from c15t if you want it.

Keep one owner per vendor

Use stable script IDs and remove the vendor's original snippet, so each vendor loads once and only through c15t. Ordinary scripts wait for permission. Helpers with alwaysLoad load at once and pass consent to the vendor's own API instead. Read the individual integration guide before assuming all helpers have the same network behavior.

Create init() or the runtime once per page. A second client, or a second loader on a kernel that @c15t/browser or createConsentRuntime owns, loads vendors twice.

Let visitors turn off one vendor

A visitor can allow marketing and still switch off one vendor in it. Declare the vendors in the vendors option and render the switch yourself; helpers from @c15t/integrations already carry their vendor slug. See vendor consent.

Clear stored tracking data

Script gating does not remove cookies or Web Storage entries that a script already wrote. Configure clear on revocation to delete them when their category is denied.

Send your own events to allowed vendors

To send your own analytics events only to the vendors a visitor allows, use the event dispatcher from @c15t/integrations. See send events only to allowed integrations.

Check it works

Run the app in a private window with the Network tab open.

  1. Filter for each vendor's host. Nothing loads before a choice.
  2. Allow one category. Only that category's vendors load.
  3. Withdraw it. The page reloads and the vendor stays absent.