@c15t/scripts 2.2.0 → 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +77 -48
- package/README.md +4 -3
- package/dist/e2e-test-utils.js +60 -24
- package/dist/engine/compile.js +45 -45
- package/dist/engine/runtime.js +130 -119
- package/dist/registry.js +196 -176
- package/dist/resolve.js +12 -12
- package/dist/vendors/_shared/attributes.js +5 -5
- package/dist/vendors/_shared/google-consent.js +10 -10
- package/dist/vendors/_shared/install-builders.js +9 -9
- package/dist/vendors/_shared/script-url.js +12 -12
- package/dist/vendors/ads-and-pixels/linkedin-insights.js +16 -16
- package/dist/vendors/ads-and-pixels/meta-pixel.js +82 -82
- package/dist/vendors/ads-and-pixels/microsoft-uet.js +57 -57
- package/dist/vendors/ads-and-pixels/openai-pixel.js +88 -0
- package/dist/vendors/ads-and-pixels/reddit-pixel.js +39 -39
- package/dist/vendors/ads-and-pixels/snapchat-pixel.js +25 -25
- package/dist/vendors/ads-and-pixels/tiktok-pixel.js +31 -31
- package/dist/vendors/ads-and-pixels/x-pixel.js +17 -17
- package/dist/vendors/analytics/adobe-analytics.js +17 -17
- package/dist/vendors/analytics/ahrefs-analytics.js +8 -8
- package/dist/vendors/analytics/amplitude.js +39 -39
- package/dist/vendors/analytics/clearbit.js +11 -11
- package/dist/vendors/analytics/cloudflare-web-analytics.js +13 -13
- package/dist/vendors/analytics/databuddy.js +45 -45
- package/dist/vendors/analytics/fathom-analytics.js +15 -15
- package/dist/vendors/analytics/google-tag.js +23 -23
- package/dist/vendors/analytics/heap.js +37 -37
- package/dist/vendors/analytics/hightouch.js +30 -30
- package/dist/vendors/analytics/hotjar.js +14 -14
- package/dist/vendors/analytics/logrocket.js +24 -24
- package/dist/vendors/analytics/matomo-analytics.js +51 -51
- package/dist/vendors/analytics/microsoft-clarity.js +34 -31
- package/dist/vendors/analytics/mixpanel-analytics.js +31 -31
- package/dist/vendors/analytics/pirsch.js +27 -27
- package/dist/vendors/analytics/plausible-analytics.js +24 -24
- package/dist/vendors/analytics/posthog.js +84 -79
- package/dist/vendors/analytics/promptwatch.js +8 -8
- package/dist/vendors/analytics/rudderstack.js +50 -50
- package/dist/vendors/analytics/rybbit-analytics.js +30 -30
- package/dist/vendors/analytics/segment.js +16 -16
- package/dist/vendors/analytics/umami-analytics.js +16 -16
- package/dist/vendors/analytics/vercel-analytics.js +22 -22
- package/dist/vendors/functional/crisp.js +49 -51
- package/dist/vendors/functional/intercom.js +18 -18
- package/dist/vendors/tag-managers/cloudflare-zaraz.js +98 -0
- package/dist/vendors/tag-managers/google-tag-manager.js +20 -20
- package/dist-types/__tests__/helpers.d.ts +11 -11
- package/dist-types/engine/compile.d.ts +3 -3
- package/dist-types/engine/runtime.d.ts +3 -3
- package/dist-types/registry.d.ts +191 -173
- package/dist-types/resolve.d.ts +3 -3
- package/dist-types/types.d.ts +2 -2
- package/dist-types/vendors/_shared/attributes.d.ts +2 -2
- package/dist-types/vendors/_shared/google-consent.d.ts +2 -2
- package/dist-types/vendors/_shared/install-builders.d.ts +2 -2
- package/dist-types/vendors/_shared/script-url.d.ts +6 -6
- package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +14 -14
- package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +27 -27
- package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +40 -40
- package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +211 -0
- package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +28 -29
- package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +23 -23
- package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +22 -22
- package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +15 -15
- package/dist-types/vendors/analytics/adobe-analytics.d.ts +3 -3
- package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +5 -5
- package/dist-types/vendors/analytics/amplitude.d.ts +24 -24
- package/dist-types/vendors/analytics/clearbit.d.ts +5 -5
- package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +6 -6
- package/dist-types/vendors/analytics/databuddy.d.ts +34 -31
- package/dist-types/vendors/analytics/fathom-analytics.d.ts +8 -8
- package/dist-types/vendors/analytics/google-tag.d.ts +17 -17
- package/dist-types/vendors/analytics/heap.d.ts +17 -17
- package/dist-types/vendors/analytics/hightouch.d.ts +15 -15
- package/dist-types/vendors/analytics/hotjar.d.ts +9 -9
- package/dist-types/vendors/analytics/logrocket.d.ts +11 -11
- package/dist-types/vendors/analytics/matomo-analytics.d.ts +3 -3
- package/dist-types/vendors/analytics/microsoft-clarity.d.ts +12 -13
- package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +20 -20
- package/dist-types/vendors/analytics/pirsch.d.ts +10 -10
- package/dist-types/vendors/analytics/plausible-analytics.d.ts +13 -13
- package/dist-types/vendors/analytics/posthog.d.ts +35 -32
- package/dist-types/vendors/analytics/promptwatch.d.ts +5 -5
- package/dist-types/vendors/analytics/rudderstack.d.ts +16 -16
- package/dist-types/vendors/analytics/rybbit-analytics.d.ts +15 -15
- package/dist-types/vendors/analytics/segment.d.ts +11 -11
- package/dist-types/vendors/analytics/umami-analytics.d.ts +9 -9
- package/dist-types/vendors/analytics/vercel-analytics.d.ts +13 -13
- package/dist-types/vendors/functional/crisp.d.ts +9 -9
- package/dist-types/vendors/functional/intercom.d.ts +12 -12
- package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +39 -0
- package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +16 -16
- package/docs/README.md +77 -48
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/customization/overview.md +45 -0
- package/docs/customization/recipes.md +79 -0
- package/docs/customization/slots.md +55 -0
- package/docs/customization/tokens.md +76 -0
- package/docs/customization/translations.md +49 -0
- package/docs/frameworks/javascript/script-loader.md +81 -343
- package/docs/frameworks/next/script-loader.md +164 -455
- package/docs/frameworks/react/script-loader.md +41 -533
- package/docs/guides/consent-state.md +60 -0
- package/docs/guides/data-fetching.md +163 -0
- package/docs/guides/deployment-modes.md +75 -0
- package/docs/guides/troubleshooting.md +68 -0
- package/docs/guides/verify-consent.md +62 -0
- package/docs/integrations/adobe-analytics.md +239 -105
- package/docs/integrations/ahrefs-analytics.md +238 -104
- package/docs/integrations/amplitude.md +219 -157
- package/docs/integrations/building-integrations.md +36 -223
- package/docs/integrations/clear-on-revocation.md +167 -0
- package/docs/integrations/clearbit.md +247 -86
- package/docs/integrations/cloudflare-web-analytics.md +250 -84
- package/docs/integrations/cloudflare-zaraz.md +399 -0
- package/docs/integrations/crisp.md +251 -97
- package/docs/integrations/databuddy.md +259 -153
- package/docs/integrations/fathom-analytics.md +239 -96
- package/docs/integrations/google-maps.md +328 -207
- package/docs/integrations/google-tag-manager.md +248 -96
- package/docs/integrations/google-tag.md +261 -90
- package/docs/integrations/granular-consent.md +208 -0
- package/docs/integrations/heap.md +222 -149
- package/docs/integrations/hightouch.md +225 -131
- package/docs/integrations/hotjar.md +239 -90
- package/docs/integrations/intercom.md +239 -98
- package/docs/integrations/linkedin-insights.md +243 -113
- package/docs/integrations/logrocket.md +241 -123
- package/docs/integrations/matomo-analytics.md +256 -111
- package/docs/integrations/meta-pixel.md +197 -324
- package/docs/integrations/microsoft-clarity.md +233 -114
- package/docs/integrations/microsoft-uet.md +245 -110
- package/docs/integrations/mixpanel-analytics.md +252 -87
- package/docs/integrations/openai-pixel.md +441 -0
- package/docs/integrations/overview.md +96 -133
- package/docs/integrations/pirsch.md +249 -96
- package/docs/integrations/plausible-analytics.md +241 -100
- package/docs/integrations/posthog.md +353 -214
- package/docs/integrations/promptwatch.md +251 -81
- package/docs/integrations/reddit-pixel.md +226 -173
- package/docs/integrations/rudderstack.md +244 -187
- package/docs/integrations/rybbit-analytics.md +244 -91
- package/docs/integrations/segment.md +238 -92
- package/docs/integrations/snapchat-pixel.md +240 -110
- package/docs/integrations/tiktok-pixel.md +249 -81
- package/docs/integrations/umami-analytics.md +242 -95
- package/docs/integrations/vercel-analytics.md +242 -90
- package/docs/integrations/x-pixel.md +238 -104
- package/docs/integrations/youtube.md +359 -142
- package/docs/upgrade-v3.md +381 -0
- package/package.json +90 -78
- package/dist/e2e-test-utils.cjs +0 -166
- package/dist/engine/compile.cjs +0 -130
- package/dist/engine/runtime.cjs +0 -475
- package/dist/registry.cjs +0 -423
- package/dist/resolve.cjs +0 -71
- package/dist/types.cjs +0 -69
- package/dist/vendors/_shared/attributes.cjs +0 -55
- package/dist/vendors/_shared/google-consent.cjs +0 -69
- package/dist/vendors/_shared/install-builders.cjs +0 -59
- package/dist/vendors/_shared/script-url.cjs +0 -78
- package/dist/vendors/ads-and-pixels/linkedin-insights.cjs +0 -89
- package/dist/vendors/ads-and-pixels/meta-pixel.cjs +0 -206
- package/dist/vendors/ads-and-pixels/microsoft-uet.cjs +0 -151
- package/dist/vendors/ads-and-pixels/reddit-pixel.cjs +0 -151
- package/dist/vendors/ads-and-pixels/snapchat-pixel.cjs +0 -131
- package/dist/vendors/ads-and-pixels/tiktok-pixel.cjs +0 -130
- package/dist/vendors/ads-and-pixels/x-pixel.cjs +0 -92
- package/dist/vendors/analytics/adobe-analytics.cjs +0 -90
- package/dist/vendors/analytics/ahrefs-analytics.cjs +0 -68
- package/dist/vendors/analytics/amplitude.cjs +0 -193
- package/dist/vendors/analytics/clearbit.cjs +0 -69
- package/dist/vendors/analytics/cloudflare-web-analytics.cjs +0 -73
- package/dist/vendors/analytics/databuddy.cjs +0 -144
- package/dist/vendors/analytics/fathom-analytics.cjs +0 -76
- package/dist/vendors/analytics/google-tag.cjs +0 -107
- package/dist/vendors/analytics/heap.cjs +0 -181
- package/dist/vendors/analytics/hightouch.cjs +0 -153
- package/dist/vendors/analytics/hotjar.cjs +0 -85
- package/dist/vendors/analytics/logrocket.cjs +0 -99
- package/dist/vendors/analytics/matomo-analytics.cjs +0 -232
- package/dist/vendors/analytics/microsoft-clarity.cjs +0 -138
- package/dist/vendors/analytics/mixpanel-analytics.cjs +0 -134
- package/dist/vendors/analytics/pirsch.cjs +0 -108
- package/dist/vendors/analytics/plausible-analytics.cjs +0 -122
- package/dist/vendors/analytics/posthog.cjs +0 -236
- package/dist/vendors/analytics/promptwatch.cjs +0 -70
- package/dist/vendors/analytics/rudderstack.cjs +0 -227
- package/dist/vendors/analytics/rybbit-analytics.cjs +0 -104
- package/dist/vendors/analytics/segment.cjs +0 -97
- package/dist/vendors/analytics/umami-analytics.cjs +0 -80
- package/dist/vendors/analytics/vercel-analytics.cjs +0 -94
- package/dist/vendors/functional/crisp.cjs +0 -143
- package/dist/vendors/functional/intercom.cjs +0 -89
- package/dist/vendors/tag-managers/google-tag-manager.cjs +0 -100
- package/docs/shared/react/guides/script-loader.md +0 -311
- package/readme.json +0 -19
|
@@ -1,501 +1,210 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description:
|
|
4
|
-
|
|
5
|
-
permission.
|
|
2
|
+
title: Load scripts with consent
|
|
3
|
+
description: Register vendor scripts in your existing Next.js ConsentRoot and
|
|
4
|
+
handle loading and revocation.
|
|
6
5
|
group: frameworks
|
|
7
6
|
---
|
|
8
|
-
The script loader manages third-party JavaScript based on consent state. You declare scripts in your provider's `scripts` option, and c15t decides when each script should load, stay loaded, unload, or receive a consent update.
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
8
|
+
## Keep one owner for vendor scripts
|
|
9
|
+
|
|
10
|
+
The [App Router](https://c15t.com/docs/frameworks/next/app-router) and
|
|
11
|
+
[Pages Router](https://c15t.com/docs/frameworks/next/pages-router) setups already register scripts
|
|
12
|
+
in a client wrapper. Keep that wrapper and add vendors to `lib/scripts.ts`.
|
|
13
|
+
If you are adding scripts to an existing c15t setup, install the helpers and
|
|
14
|
+
use the same registration pattern below.
|
|
15
|
+
|
|
16
|
+
| Package manager | Command |
|
|
17
|
+
| :-------------- | :-------------------------- |
|
|
18
|
+
| npm | `npm install @c15t/scripts` |
|
|
19
|
+
| pnpm | `pnpm add @c15t/scripts` |
|
|
20
|
+
| yarn | `yarn add @c15t/scripts` |
|
|
21
|
+
| bun | `bun add @c15t/scripts` |
|
|
22
|
+
|
|
23
|
+
Keep `c15t.config.ts` and the manifest route from your router setup. These shared
|
|
24
|
+
URLs connect initialization and consent submissions to the same backend.
|
|
25
|
+
|
|
26
|
+
## Register scripts in a client wrapper
|
|
27
|
+
|
|
28
|
+
The [runnable Next.js example](https://c15t.com/docs/examples) uses PostHog for measurement and
|
|
29
|
+
X Pixel for marketing. Set `NEXT_PUBLIC_POSTHOG_KEY` and
|
|
30
|
+
`NEXT_PUBLIC_X_PIXEL_ID` to your own project identifiers before building.
|
|
31
|
+
`NEXT_PUBLIC_POSTHOG_HOST` optionally selects your PostHog region's API host.
|
|
32
|
+
Omit a vendor's ID to leave that integration disabled, or replace its helper
|
|
33
|
+
with the [integration](../../integrations/overview.md) your application uses. Include the
|
|
34
|
+
measurement and marketing categories in your policy for these two vendors.
|
|
35
|
+
|
|
36
|
+
Create `lib/scripts.ts` with the example's script configuration:
|
|
37
|
+
|
|
38
|
+
```ts title="lib/scripts.ts"
|
|
39
|
+
import { posthog } from '@c15t/scripts/posthog';
|
|
40
|
+
import { xPixel } from '@c15t/scripts/x-pixel';
|
|
41
|
+
import type { Script } from 'c15t';
|
|
42
|
+
|
|
43
|
+
export const posthogConfigured = Boolean(process.env.NEXT_PUBLIC_POSTHOG_KEY);
|
|
44
|
+
export const xPixelConfigured = Boolean(process.env.NEXT_PUBLIC_X_PIXEL_ID);
|
|
45
|
+
|
|
46
|
+
export const scripts: Script[] = [];
|
|
47
|
+
|
|
48
|
+
if (process.env.NEXT_PUBLIC_POSTHOG_KEY) {
|
|
49
|
+
scripts.push(
|
|
50
|
+
posthog({
|
|
51
|
+
apiHost: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
|
52
|
+
id: process.env.NEXT_PUBLIC_POSTHOG_KEY,
|
|
53
|
+
initOptions: { cookieless_mode: 'never' },
|
|
54
|
+
loadMode: 'after-consent',
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
31
58
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<ConsentManagerProvider
|
|
35
|
-
options={{
|
|
36
|
-
mode: 'hosted',
|
|
37
|
-
backendURL: '/api/c15t',
|
|
38
|
-
scripts: [
|
|
39
|
-
metaPixel({ pixelId: '123456' }),
|
|
40
|
-
{
|
|
41
|
-
id: 'custom-analytics',
|
|
42
|
-
src: 'https://cdn.example.com/analytics.js',
|
|
43
|
-
category: 'measurement',
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
}}
|
|
47
|
-
>
|
|
48
|
-
{children}
|
|
49
|
-
</ConsentManagerProvider>
|
|
50
|
-
);
|
|
59
|
+
if (process.env.NEXT_PUBLIC_X_PIXEL_ID) {
|
|
60
|
+
scripts.push(xPixel({ pixelId: process.env.NEXT_PUBLIC_X_PIXEL_ID }));
|
|
51
61
|
}
|
|
52
62
|
```
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
PostHog waits for measurement consent here. `cookieless_mode: 'never'` disables
|
|
65
|
+
cookieless capture after rejection. X Pixel waits for marketing consent. Remove
|
|
66
|
+
any existing loader for these vendors, including `next/script` and tag-manager
|
|
67
|
+
entries, so each integration loads once.
|
|
57
68
|
|
|
58
|
-
|
|
69
|
+
Create this client wrapper. It keeps scripts and browser callbacks in the client
|
|
70
|
+
while the router supplies the visitor's resolved state through `state`:
|
|
59
71
|
|
|
60
|
-
```tsx
|
|
72
|
+
```tsx title="components/consent.tsx"
|
|
61
73
|
'use client';
|
|
62
74
|
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
import type { ReactNode } from 'react';
|
|
76
|
+
import {
|
|
77
|
+
ConsentBanner,
|
|
78
|
+
ConsentDialog,
|
|
79
|
+
ConsentDialogLink,
|
|
80
|
+
ConsentRoot,
|
|
81
|
+
} from 'c15t/next';
|
|
82
|
+
import type { ConsentRootProps } from 'c15t/next';
|
|
83
|
+
import { consentConfig } from '../c15t.config';
|
|
84
|
+
import { scripts } from '../lib/scripts';
|
|
85
|
+
|
|
86
|
+
export function Consent({
|
|
87
|
+
children,
|
|
88
|
+
state,
|
|
89
|
+
}: {
|
|
90
|
+
children: ReactNode;
|
|
91
|
+
state: ConsentRootProps['state'];
|
|
92
|
+
}) {
|
|
74
93
|
return (
|
|
75
|
-
<
|
|
76
|
-
options={{
|
|
77
|
-
mode: 'hosted',
|
|
78
|
-
backendURL: '/api/c15t',
|
|
79
|
-
scripts,
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
94
|
+
<ConsentRoot state={state} config={consentConfig} scripts={scripts}>
|
|
82
95
|
{children}
|
|
83
|
-
|
|
96
|
+
<ConsentBanner />
|
|
97
|
+
<ConsentDialog />
|
|
98
|
+
<footer>
|
|
99
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
100
|
+
</footer>
|
|
101
|
+
</ConsentRoot>
|
|
84
102
|
);
|
|
85
103
|
}
|
|
86
104
|
```
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
## Mental Model
|
|
91
|
-
|
|
92
|
-
Every script you register has the same lifecycle. c15t evaluates each script against the current consent state, then drives it through a small number of states:
|
|
93
|
-
|
|
94
|
-
1. **Pending** — registered but waiting for consent. Nothing is in the DOM yet.
|
|
95
|
-
2. **Loaded** — consent matched, c15t injected the script (or ran callbacks for callback-only scripts).
|
|
96
|
-
3. **Updated** — already loaded, consent state changed, `onConsentChange` ran so the SDK can react.
|
|
97
|
-
4. **Unloaded** — consent was revoked. c15t removed the script element unless you opted into persistence.
|
|
98
|
-
|
|
99
|
-
Four lifecycle callbacks let you hook into transitions: `onBeforeLoad`, `onLoad`, `onConsentChange`, and `onError`. Two flags — [`alwaysLoad`](#always-load) and [`persistAfterConsentRevoked`](#persist-after-revocation) — change how c15t treats consent boundaries. Everything else (DOM placement, ad-block evasion, dynamic management) is a refinement on top of this core model.
|
|
100
|
-
|
|
101
|
-
## Choose the Right Approach
|
|
102
|
-
|
|
103
|
-
Most projects mix more than one style. Pick the smallest one that keeps consent behavior obvious:
|
|
104
|
-
|
|
105
|
-
|Style|Use when|
|
|
106
|
-
|--|--|
|
|
107
|
-
|**Built-in helper** from `@c15t/scripts`|c15t already ships the vendor. See the [integrations overview](/docs/integrations/overview).|
|
|
108
|
-
|**Plain `Script`**|One-off app code with simple load and callback behavior.|
|
|
109
|
-
|**Callback-only `Script`**|Another package already loaded the SDK; c15t only synchronizes consent.|
|
|
110
|
-
|**Manifest-backed helper**|Reusable vendor integration with structured setup phases, queues, stubs, or a vendor consent API.|
|
|
111
|
-
|**Iframe / renderable integration**|Vendor exposes an iframe or React component, not just a `<script>` tag.|
|
|
112
|
-
|
|
113
|
-
## Script Types
|
|
114
|
-
|
|
115
|
-
### Standard Scripts
|
|
116
|
-
|
|
117
|
-
Standard scripts load an external JavaScript file via a `<script>` tag. This is the default for most analytics and pixel SDKs:
|
|
118
|
-
|
|
119
|
-
```tsx
|
|
120
|
-
{
|
|
121
|
-
id: 'analytics',
|
|
122
|
-
src: 'https://cdn.example.com/analytics.js',
|
|
123
|
-
category: 'measurement',
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Inline Scripts
|
|
128
|
-
|
|
129
|
-
Inline scripts execute JavaScript from `textContent` instead of loading a URL. Use these sparingly; a manifest-backed helper is usually better for reusable vendor code.
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
{
|
|
133
|
-
id: 'gtag-config',
|
|
134
|
-
textContent: `
|
|
135
|
-
window.dataLayer = window.dataLayer || [];
|
|
136
|
-
function gtag(){dataLayer.push(arguments);}
|
|
137
|
-
gtag('js', new Date());
|
|
138
|
-
gtag('config', 'G-XXXXXX');
|
|
139
|
-
`,
|
|
140
|
-
category: 'measurement',
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Callback-Only Scripts
|
|
106
|
+
`ConsentRoot` already provides the consent runtime. Mount this wrapper once;
|
|
107
|
+
do not add a second provider. Keep your site's content and footer inside it.
|
|
145
108
|
|
|
146
|
-
|
|
109
|
+
## Pass prepared consent through your router
|
|
147
110
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
category: 'measurement',
|
|
153
|
-
onLoad: ({ hasConsent }) => {
|
|
154
|
-
if (hasConsent) {
|
|
155
|
-
posthog.opt_in_capturing();
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
onConsentChange: ({ hasConsent }) => {
|
|
159
|
-
if (hasConsent) {
|
|
160
|
-
posthog.opt_in_capturing();
|
|
161
|
-
} else {
|
|
162
|
-
posthog.opt_out_capturing();
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Manifest-Backed Helpers
|
|
169
|
-
|
|
170
|
-
Built-in integrations in `@c15t/scripts` are manifest-backed. A manifest describes vendor setup as structured phases, then c15t compiles it into a `Script`. Manifests keep queue stubs, script URLs, consent signaling, and post-load work consistent across apps and they are safe to ship from a server.
|
|
171
|
-
|
|
172
|
-
Use a manifest-backed helper when:
|
|
173
|
-
|
|
174
|
-
* the integration should be reused across projects,
|
|
175
|
-
* the vendor snippet has ordered setup steps,
|
|
176
|
-
* the vendor exposes a consent API,
|
|
177
|
-
* or you plan to contribute the integration back to c15t.
|
|
178
|
-
|
|
179
|
-
Read the [custom integration guide](/docs/integrations/building-integrations) for the manifest contract, phases, and testing checklist.
|
|
180
|
-
|
|
181
|
-
### Iframe And Renderable Integrations
|
|
182
|
-
|
|
183
|
-
Some vendors are not just script tags. YouTube embeds, maps, calendars, and checkout widgets often need a visible component, a placeholder, or an iframe.
|
|
184
|
-
|
|
185
|
-
* For iframe-only embeds, gate the iframe `src` with the [iframe blocking](/docs/frameworks/react/iframe-blocking) pattern instead of loading a script just to hide an iframe.
|
|
186
|
-
* For SDK-backed UI, use the script loader for the shared SDK and render the component only when consent and SDK readiness agree.
|
|
187
|
-
* Use `YouTubeEmbed` for the iframe-only YouTube candidate and `GoogleMap` for the callback-based SDK candidate.
|
|
188
|
-
* Use `useConsentScript()` when building custom wrappers. It registers scripts through the consent store, follows `loadedScripts`, and returns a promise-shaped readiness contract for callback-based SDKs.
|
|
189
|
-
|
|
190
|
-
## Lifecycle Callbacks
|
|
191
|
-
|
|
192
|
-
Every script supports four callbacks. Each receives a `ScriptCallbackInfo` payload (id, element, hasConsent, consents):
|
|
193
|
-
|
|
194
|
-
* `onBeforeLoad` — runs before the script tag is injected. Create globals, queues, or vendor stubs here.
|
|
195
|
-
* `onLoad` — runs after the browser loads the script. Call vendor `init()` APIs here.
|
|
196
|
-
* `onConsentChange` — runs for loaded scripts when consent changes. Forward the new consent state to the vendor SDK.
|
|
197
|
-
* `onError` — runs when the script fails to load. Record diagnostics or render a fallback.
|
|
111
|
+
App Router awaits the prefetch in the `ResolvedConsent` Server Component from
|
|
112
|
+
the [App Router guide](https://c15t.com/docs/frameworks/next/app-router) and renders the
|
|
113
|
+
wrapper inside it. This partial example is that component; keep the
|
|
114
|
+
`Suspense` boundary, `html`, `body` and stylesheet from your existing layout:
|
|
198
115
|
|
|
199
116
|
```tsx
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
window.analytics.init('my-key');
|
|
209
|
-
},
|
|
210
|
-
onError: ({ error }) => {
|
|
211
|
-
console.error('Failed to load analytics:', error);
|
|
212
|
-
},
|
|
213
|
-
onConsentChange: ({ hasConsent }) => {
|
|
214
|
-
window.analytics.setConsent(hasConsent);
|
|
215
|
-
},
|
|
117
|
+
import type { ReactNode } from 'react';
|
|
118
|
+
import { resolveConsent } from 'c15t/next/server';
|
|
119
|
+
import { consentConfig } from '../c15t.config';
|
|
120
|
+
import { Consent } from '../components/consent';
|
|
121
|
+
|
|
122
|
+
async function ResolvedConsent({ children }: { children: ReactNode }) {
|
|
123
|
+
const state = await resolveConsent({ config: consentConfig });
|
|
124
|
+
return <Consent state={state}>{children}</Consent>;
|
|
216
125
|
}
|
|
217
126
|
```
|
|
218
127
|
|
|
219
|
-
|
|
128
|
+
To stream the page shell before consent resolves instead, pass the unawaited
|
|
129
|
+
promise from a synchronous layout as described in
|
|
130
|
+
[stream the page while consent resolves](https://c15t.com/docs/frameworks/next/app-router#stream-the-page-while-consent-resolves).
|
|
220
131
|
|
|
221
|
-
|
|
132
|
+
Pages Router passes `state={pageProps.consentState ?? {}}` to this wrapper
|
|
133
|
+
in `_app.tsx`. Keep `getServerSideProps` and its `c15t/next/pages` helper.
|
|
134
|
+
The router guides contain complete layout and `_app.tsx` files.
|
|
222
135
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
136
|
+
For static export or browser-only initialization, pass `state={{}}` and keep
|
|
137
|
+
that setup's existing transport. Register scripts on its existing `ConsentRoot`;
|
|
138
|
+
do not introduce server prefetch or local routes just to add a vendor.
|
|
226
139
|
|
|
227
|
-
|
|
228
|
-
{ category: { and: ['measurement', 'marketing'] } }
|
|
140
|
+
## Check each vendor's loading behavior
|
|
229
141
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
142
|
+
The example explicitly configures PostHog to load after consent and disables
|
|
143
|
+
cookieless capture. Those settings are deliberate; its default helper can load
|
|
144
|
+
before consent and use the SDK's consent controls. Read the
|
|
145
|
+
[PostHog guide](../../integrations/posthog.md) before changing them.
|
|
233
146
|
|
|
234
|
-
|
|
147
|
+
Ordinary scripts wait for their category's effective permission. Give each
|
|
148
|
+
script a stable unique `id` and remove any other loader for the same vendor.
|
|
149
|
+
Helpers with `alwaysLoad` may load an SDK before permission is granted; a
|
|
150
|
+
category field alone does not guarantee no requests. See the
|
|
151
|
+
[vendor guides](../../integrations/overview.md) for their exact contracts.
|
|
235
152
|
|
|
236
|
-
|
|
153
|
+
Removing a script element cannot undo executed JavaScript or requests already
|
|
154
|
+
sent. PostHog exposes capture controls; X Pixel has no consent-update API.
|
|
155
|
+
Stop future event calls after revocation and test a change from allowed to
|
|
156
|
+
denied, as well as initial denial.
|
|
237
157
|
|
|
238
|
-
|
|
158
|
+
## Verify the integration
|
|
239
159
|
|
|
240
|
-
|
|
160
|
+
With an opt-in policy and no saved choice, neither configured example vendor
|
|
161
|
+
should load. Allow measurement only: PostHog loads and X Pixel remains blocked.
|
|
162
|
+
Reject, reload, and reopen Privacy settings to confirm the choice persists.
|
|
241
163
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
alwaysLoad: true,
|
|
248
|
-
}
|
|
249
|
-
```
|
|
164
|
+
Use the [runnable example](https://c15t.com/docs/examples) to inspect the same script definitions
|
|
165
|
+
with DevTools, or follow [verification](../../guides/verify-consent.md) in your app.
|
|
166
|
+
Use [custom integrations](../../integrations/building-integrations.md) for an
|
|
167
|
+
unlisted vendor. Google helpers have a separate
|
|
168
|
+
[Consent Mode contract](../../integrations/google-tag-manager.md).
|
|
250
169
|
|
|
251
|
-
|
|
170
|
+
## Granular consent
|
|
252
171
|
|
|
253
|
-
|
|
254
|
-
|
|
172
|
+
A visitor can grant marketing and still turn one vendor off. Declare the
|
|
173
|
+
vendors next to the scripts in `lib/scripts.ts`, with the `vendor` slug each
|
|
174
|
+
script already carries, and pass both to `ConsentRoot`:
|
|
255
175
|
|
|
256
|
-
|
|
176
|
+
```ts title="lib/scripts.ts"
|
|
177
|
+
import type { Vendor } from 'c15t';
|
|
257
178
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
category: 'measurement',
|
|
265
|
-
persistAfterConsentRevoked: true,
|
|
266
|
-
onConsentChange: ({ hasConsent }) => {
|
|
267
|
-
window.ErrorTracker.setConsent(hasConsent);
|
|
179
|
+
export const vendors: Vendor[] = [
|
|
180
|
+
{
|
|
181
|
+
id: 'x-pixel',
|
|
182
|
+
name: 'X Pixel',
|
|
183
|
+
category: 'marketing',
|
|
184
|
+
privacyPolicyUrl: 'https://x.com/privacy',
|
|
268
185
|
},
|
|
269
|
-
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
As with `alwaysLoad`, `onConsentChange` is how the persisted SDK learns about consent updates.
|
|
273
|
-
|
|
274
|
-
### `alwaysLoad` vs `persistAfterConsentRevoked`
|
|
275
|
-
|
|
276
|
-
These two flags answer different questions. Use this table to keep them straight:
|
|
277
|
-
|
|
278
|
-
|Question|`alwaysLoad`|`persistAfterConsentRevoked`|
|
|
279
|
-
|--|--|--|
|
|
280
|
-
|Loads before consent is granted?|Yes|No (waits for consent like a normal script)|
|
|
281
|
-
|Stays loaded after consent is revoked?|Yes|Yes|
|
|
282
|
-
|Requires a vendor consent API?|Yes|Yes|
|
|
283
|
-
|
|
284
|
-
## DOM Placement
|
|
285
|
-
|
|
286
|
-
Control where the script is injected and whether the element id is anonymized:
|
|
287
|
-
|
|
288
|
-
```tsx
|
|
289
|
-
{
|
|
290
|
-
id: 'widget',
|
|
291
|
-
src: 'https://widget.example.com/embed.js',
|
|
292
|
-
category: 'experience',
|
|
293
|
-
target: 'body', // 'head' (default) or 'body'
|
|
294
|
-
anonymizeId: true, // default: true, hides the c15t script id from ad blockers
|
|
295
|
-
nonce: 'abc123', // optional CSP nonce
|
|
296
|
-
}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
Set `anonymizeId: false` only when another script or test needs a stable DOM id. Pass `nonce` when your CSP requires it; c15t applies it directly to the generated `<script>` element.
|
|
300
|
-
|
|
301
|
-
You usually do not need a per-script `nonce`. Setting `nonce` once on the provider covers every injected script (and the theme stylesheet); a per-script value overrides it for that script alone.
|
|
302
|
-
|
|
303
|
-
## Dynamic Management
|
|
304
|
-
|
|
305
|
-
Framework packages expose script-manager methods so integrations can be added, removed, or inspected at runtime. Use this for tenant-specific tools, feature-flagged scripts, or vendors that are configured after sign-in:
|
|
306
|
-
|
|
307
|
-
* `setScripts(scripts)` — registers script definitions and immediately evaluates them against consent.
|
|
308
|
-
* `removeScript(id)` — removes a definition and unloads its element if needed.
|
|
309
|
-
* `isScriptLoaded(id)` — returns whether c15t has loaded a script.
|
|
310
|
-
* `getLoadedScriptIds()` — returns every currently loaded script id.
|
|
311
|
-
|
|
312
|
-
Dynamic scripts should still use stable ids. If the same vendor is added repeatedly with different ids, c15t treats each call as a new script.
|
|
313
|
-
|
|
314
|
-
## Calling Vendor APIs From Your App
|
|
315
|
-
|
|
316
|
-
The script loader controls **when the vendor SDK loads**. It does not intercept calls your application code makes to that SDK afterwards. Whether your event calls are safe before consent is granted depends on the script's persistence flags:
|
|
317
|
-
|
|
318
|
-
|Vendor pattern|What c15t does|What your app code must do|
|
|
319
|
-
|--|--|--|
|
|
320
|
-
|Consent-gated load, unloaded on revoke (e.g. cookieless analytics)|Script not in DOM until consent granted; removed on revoke. Global is `undefined` outside that window.|**Guard every call.** Unguarded `window.vendor.track(...)` throws when the global is absent.|
|
|
321
|
-
|Consent-gated load with `persistAfterConsentRevoked` (e.g. Meta Pixel)|Script not in DOM until consent granted; stays after revoke. c15t calls vendor's consent-revoke API on revocation.|Guard calls only for the pre-initial-consent window. Once loaded, the SDK handles its own suppression.|
|
|
322
|
-
|`alwaysLoad: true` with a vendor consent API (e.g. GTM, gtag, Databuddy, PostHog)|Script in DOM on page start; c15t signals consent state through the vendor's API.|Calls are safe — the vendor SDK suppresses transmission when consent is denied.|
|
|
323
|
-
|No app-facing API (e.g. Cloudflare Web Analytics)|Script in/out of DOM based on consent. Tracking is fully automatic.|Nothing to guard.|
|
|
324
|
-
|
|
325
|
-
The safe pattern in React is to read consent state through `useConsentManager().has(category)` before calling the SDK:
|
|
326
|
-
|
|
327
|
-
```tsx
|
|
328
|
-
import { useCallback } from 'react';
|
|
329
|
-
import { useConsentManager } from '@c15t/react';
|
|
330
|
-
|
|
331
|
-
function useTrackSignup() {
|
|
332
|
-
const { has } = useConsentManager();
|
|
333
|
-
|
|
334
|
-
return useCallback(() => {
|
|
335
|
-
if (has('measurement')) {
|
|
336
|
-
window.fathom?.trackEvent('signup');
|
|
337
|
-
}
|
|
338
|
-
}, [has]);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function SignupButton() {
|
|
342
|
-
const trackSignup = useTrackSignup();
|
|
343
|
-
|
|
344
|
-
return <button onClick={trackSignup}>Sign up</button>;
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
From non-React code, read the consent store directly:
|
|
349
|
-
|
|
350
|
-
```ts
|
|
351
|
-
import { getOrCreateConsentRuntime } from 'c15t';
|
|
352
|
-
|
|
353
|
-
const { consentStore } = getOrCreateConsentRuntime();
|
|
354
|
-
|
|
355
|
-
if (consentStore.getState().has('measurement')) {
|
|
356
|
-
window.fathom?.trackEvent('signup');
|
|
357
|
-
}
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
Each [integration page](/docs/integrations/overview) includes a vendor-specific **Tracking events in your app** block that names which pattern applies.
|
|
361
|
-
|
|
362
|
-
## Debugging Checklist
|
|
363
|
-
|
|
364
|
-
When a script does not behave as expected:
|
|
365
|
-
|
|
366
|
-
1. Confirm the script's `category` matches the consent that has been granted.
|
|
367
|
-
2. Check whether the script is `alwaysLoad` or consent-gated.
|
|
368
|
-
3. Confirm `onBeforeLoad` creates any globals before the vendor code reads them.
|
|
369
|
-
4. Confirm `onConsentChange` updates persisted or always-loaded scripts when consent changes.
|
|
370
|
-
5. Check whether the browser or an ad blocker blocked the request.
|
|
371
|
-
6. Use c15t devtools to inspect script lifecycle events when available.
|
|
372
|
-
|
|
373
|
-
## Dynamic Script Management
|
|
374
|
-
|
|
375
|
-
The shared guide above lists what the script-manager methods do. In Next.js they are exposed through `useConsentManager()`:
|
|
376
|
-
|
|
377
|
-
```tsx
|
|
378
|
-
import { useConsentManager } from '@c15t/nextjs';
|
|
379
|
-
|
|
380
|
-
function ScriptManager() {
|
|
381
|
-
const {
|
|
382
|
-
setScripts,
|
|
383
|
-
removeScript,
|
|
384
|
-
isScriptLoaded,
|
|
385
|
-
getLoadedScriptIds,
|
|
386
|
-
} = useConsentManager();
|
|
387
|
-
|
|
388
|
-
// ...
|
|
389
|
-
}
|
|
186
|
+
];
|
|
390
187
|
```
|
|
391
188
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
```tsx
|
|
395
|
-
'use client';
|
|
396
|
-
|
|
397
|
-
import { useEffect } from 'react';
|
|
398
|
-
import { useConsentManager } from '@c15t/nextjs';
|
|
399
|
-
|
|
400
|
-
export function WorkspaceAnalytics({ workspaceId }: { workspaceId: string }) {
|
|
401
|
-
const { setScripts, removeScript } = useConsentManager();
|
|
189
|
+
```tsx title="components/consent.tsx"
|
|
190
|
+
import { scripts, vendors } from '../lib/scripts';
|
|
402
191
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
setScripts([
|
|
407
|
-
{
|
|
408
|
-
id: scriptId,
|
|
409
|
-
src: `https://cdn.example.com/workspaces/${workspaceId}.js`,
|
|
410
|
-
category: 'measurement',
|
|
411
|
-
},
|
|
412
|
-
]);
|
|
413
|
-
|
|
414
|
-
return () => {
|
|
415
|
-
removeScript(scriptId);
|
|
416
|
-
};
|
|
417
|
-
}, [workspaceId, setScripts, removeScript]);
|
|
418
|
-
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
192
|
+
<ConsentRoot state={state} config={consentConfig} scripts={scripts} vendors={vendors}>
|
|
193
|
+
{children}
|
|
194
|
+
</ConsentRoot>
|
|
421
195
|
```
|
|
422
196
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
`@c15t/nextjs` in client components:
|
|
430
|
-
|
|
431
|
-
```tsx
|
|
432
|
-
'use client';
|
|
433
|
-
|
|
434
|
-
import { GoogleMap, YouTubeEmbed } from '@c15t/nextjs';
|
|
435
|
-
|
|
436
|
-
export function ConsentAwareMedia() {
|
|
437
|
-
const googleMapsApiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;
|
|
438
|
-
|
|
439
|
-
return (
|
|
440
|
-
<>
|
|
441
|
-
{googleMapsApiKey && (
|
|
442
|
-
<GoogleMap
|
|
443
|
-
apiKey={googleMapsApiKey}
|
|
444
|
-
authReferrerPolicy="origin"
|
|
445
|
-
center={{ lat: 40.7128, lng: -74.006 }}
|
|
446
|
-
className="overflow-hidden rounded-xl"
|
|
447
|
-
consentCategory="measurement"
|
|
448
|
-
zoom={12}
|
|
449
|
-
/>
|
|
450
|
-
)}
|
|
451
|
-
|
|
452
|
-
<YouTubeEmbed
|
|
453
|
-
consentCategory="marketing"
|
|
454
|
-
title="Product demo"
|
|
455
|
-
videoId="dQw4w9WgXcQ"
|
|
456
|
-
/>
|
|
457
|
-
</>
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
The implementation splits the problem into three layers:
|
|
463
|
-
|
|
464
|
-
1. Use the script loader as the source of truth for shared SDK loading.
|
|
465
|
-
2. Use `useConsentScript()` for SDK readiness when a vendor uses a callback-style loader.
|
|
466
|
-
3. Create the widget instance only on the client and clean it up on unmount.
|
|
467
|
-
|
|
468
|
-
Google Maps is SDK-backed and uses one shared c15t-managed script plus per-component map instances. The script is never requested before consent. Once loaded, its manager registration is retained to respect Google's one-loader-per-page constraint, while each map instance is destroyed on unmount or when consent becomes unavailable. The component adopts an existing Google Maps global, has a visible `320px` default height, uses the official API types, and reports loader, authentication, and constructor failures through `onError` and `errorFallback`.
|
|
469
|
-
|
|
470
|
-
All maps with the same `scriptId` must use the same loader configuration. `mapId` changes recreate the instance; `center`, `zoom`, and updateable `options` update it in place. You can pass Google's direct-loader options through `libraries`, `language`, `region`, `version`, `authReferrerPolicy`, `mapIds`, `channel`, and `solutionChannel`.
|
|
471
|
-
|
|
472
|
-
YouTube is iframe-only and uses the existing `Frame` boundary rather than the
|
|
473
|
-
YouTube iframe API. It defaults to a responsive, borderless 16:9 frame with a
|
|
474
|
-
localized loading state, stable placeholder dimensions, native lazy loading,
|
|
475
|
-
and the privacy-enhanced host. Its `className` and forwarded ref target the
|
|
476
|
-
iframe; `wrapperClassName` and `frameProps` target the `Frame`. Boolean `params`
|
|
477
|
-
become `1` or `0`.
|
|
478
|
-
|
|
479
|
-
For a custom client-side SDK, `useConsentScript()` exposes `blocked`, `loading`,
|
|
480
|
-
`ready`, and `error` states plus `readyValue`, `error`, and `ready`. Change its
|
|
481
|
-
`retryKey` after a failed attempt to retry the same script id. Registrations
|
|
482
|
-
with one id must use compatible script and readiness options; conflicts surface
|
|
483
|
-
as `ConsentScriptConflictError`.
|
|
484
|
-
|
|
485
|
-
For iframe-only embeds, use the [iframe blocking](/docs/frameworks/next/iframe-blocking) pattern or `YouTubeEmbed`. For SDK-backed widgets, load the SDK once and let each component instance create and clean up its own widget. Avoid mixing `next/script` with c15t for the same vendor.
|
|
486
|
-
|
|
487
|
-
## App Router Notes
|
|
488
|
-
|
|
489
|
-
* The provider and any component that calls `useConsentManager()` must be client components.
|
|
490
|
-
* Keep vendor ids out of static examples when they differ per environment — read them from `process.env.NEXT_PUBLIC_*` or runtime config.
|
|
491
|
-
* If a script must be available before a page becomes interactive, prefer a built-in helper that models denied-consent defaults rather than adding a separate `next/script` tag.
|
|
492
|
-
* If you use CSP nonces, set `nonce` on the provider options so c15t applies it to every injected script element and the theme stylesheet. See [Content Security Policy](/docs/frameworks/next/components/consent-manager-provider#content-security-policy) for reading the nonce in the App Router.
|
|
493
|
-
|
|
494
|
-
## API Reference
|
|
197
|
+
The preference center lists each vendor under its category with a switch.
|
|
198
|
+
Integrations from `@c15t/scripts` set `vendor` to their manifest slug, so
|
|
199
|
+
`xPixel()` needs no extra wiring; give a hand-written script the same slug
|
|
200
|
+
in its `vendor` field. A backend manifest can declare vendors too. See
|
|
201
|
+
[granular consent](../../integrations/granular-consent.md) for storage,
|
|
202
|
+
bulk actions and the hooks a custom control uses.
|
|
495
203
|
|
|
496
|
-
|
|
497
|
-
|:--|:--|
|
|
498
|
-
|Type Name|\`Script\`|
|
|
499
|
-
|Source Path|\`./packages/core/src/libs/script-loader/types.ts\`|
|
|
204
|
+
## Clear stored tracking data
|
|
500
205
|
|
|
501
|
-
|
|
206
|
+
Script gating does not remove cookies or Web Storage entries that a script
|
|
207
|
+
already wrote. Add `clearOnRevocation` to your `ConsentRoot` or provider
|
|
208
|
+
options to remove declared data when its category is denied. See
|
|
209
|
+
[clear on revocation](../../integrations/clear-on-revocation.md) for configuration
|
|
210
|
+
and browser limits.
|