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:
| Setup | Where scripts goes |
|---|---|
@c15t/browser | init({ scripts, … }), as in the quickstart. |
c15t/runtime | createConsentRuntime({ mode, scripts }), as in headless. |
| Your own kernel | createScriptLoader({ kernel, scripts }). See script loader. |
The quickstart's src/main.ts shows PostHog:
Each integration guide gives the helper and options for one vendor. For a vendor without a helper, write the object yourself:
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.
- Filter for each vendor's host. Nothing loads before a choice.
- Allow one category. Only that category's vendors load.
- Withdraw it. The page reloads and the vendor stays absent.