@c15t/react 3.0.0-alpha.5 → 3.0.0-alpha.7

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.
Files changed (77) hide show
  1. package/AGENTS.md +6 -4
  2. package/SKILL.md +1 -1
  3. package/dist/component-hooks/use-headless-consent-ui.js +1 -1
  4. package/dist/components/iab-panel/atoms/vendor-list.js +1 -1
  5. package/dist/components/iab-panel/hooks/use-gvl-data.js +1 -1
  6. package/dist/components/iab-panel/index.js +1 -1
  7. package/dist/components/iab-prompt/atoms/overlay.js +1 -1
  8. package/dist/components/iab-prompt/atoms/root.js +1 -1
  9. package/dist/components/panel/index.js +1 -1
  10. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  11. package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -1
  12. package/dist/components/panel-trigger/panel-trigger.js +1 -1
  13. package/dist/components/preferences/index.js +1 -1
  14. package/dist/components/prompt/atoms/overlay.js +1 -1
  15. package/dist/components/prompt/atoms/root.js +1 -1
  16. package/dist/components/shared/ui/accordion/accordion.js +1 -1
  17. package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
  18. package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
  19. package/dist/components/shared/ui/switch/switch.js +1 -1
  20. package/dist/components/shared/ui/tabs/tabs.js +1 -1
  21. package/dist/devtools-launcher.js +2 -0
  22. package/dist/devtools.js +1 -1
  23. package/dist/hooks/use-headless-iab-consent-ui.js +1 -1
  24. package/dist/hooks/use-late-entry.js +2 -0
  25. package/dist/iab/styles.tw3.css +2 -2
  26. package/dist/index-parts/core.js +1 -1
  27. package/dist/provider.js +1 -1
  28. package/dist/styles.tw3.css +29 -5
  29. package/dist/version.js +1 -1
  30. package/dist-types/component-hooks/use-headless-consent-ui.d.ts +4 -4
  31. package/dist-types/components/iab-panel/index.d.ts +0 -1
  32. package/dist-types/components/panel/index.d.ts +0 -1
  33. package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +5 -0
  34. package/dist-types/components/panel-trigger/panel-trigger.d.ts +0 -8
  35. package/dist-types/components/preferences/index.d.ts +0 -1
  36. package/dist-types/components/shared/ui/accordion/accordion.d.ts +0 -1
  37. package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +0 -1
  38. package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +0 -1
  39. package/dist-types/components/shared/ui/switch/switch.d.ts +0 -1
  40. package/dist-types/components/shared/ui/tabs/tabs.d.ts +0 -1
  41. package/dist-types/devtools-launcher.d.ts +29 -0
  42. package/dist-types/hooks/use-iab-manager.d.ts +4 -4
  43. package/dist-types/hooks/use-late-entry.d.ts +14 -0
  44. package/dist-types/hooks.d.ts +27 -5
  45. package/dist-types/index-parts/core.d.ts +1 -1
  46. package/dist-types/index.d.ts +1 -1
  47. package/dist-types/provider.d.ts +23 -4
  48. package/dist-types/version.d.ts +1 -1
  49. package/docs/README.md +6 -4
  50. package/docs/concepts/choose-your-setup.md +23 -6
  51. package/docs/concepts/consent-state.md +47 -0
  52. package/docs/concepts/data-fetching.md +68 -11
  53. package/docs/customization/motion.md +22 -13
  54. package/docs/customization/recipes.md +4 -5
  55. package/docs/customization/slots.md +3 -2
  56. package/docs/customization/stylesheets.md +29 -39
  57. package/docs/customization/tailwind.md +5 -5
  58. package/docs/customization/tokens.md +2 -1
  59. package/docs/customization/translations.md +32 -5
  60. package/docs/frameworks/react/components/consent-dialog-trigger.md +5 -0
  61. package/docs/frameworks/react/components/consent-dialog.md +2 -1
  62. package/docs/frameworks/react/components/consent-provider.md +29 -28
  63. package/docs/frameworks/react/components/dev-tools.md +23 -0
  64. package/docs/frameworks/react/headless.md +4 -4
  65. package/docs/frameworks/react/hooks.md +10 -5
  66. package/docs/frameworks/react/iab.md +24 -18
  67. package/docs/frameworks/react/quickstart.md +77 -16
  68. package/docs/frameworks/react/rendering.md +82 -11
  69. package/docs/frameworks/react/scripts.md +8 -3
  70. package/docs/frameworks/react/upgrade-v3.md +596 -0
  71. package/docs/frameworks/react/vendor-consent.md +6 -0
  72. package/docs/guides/banner-experiments.md +26 -5
  73. package/docs/guides/troubleshooting.md +3 -2
  74. package/docs/integrations/overview.md +28 -27
  75. package/docs/integrations/sentry.md +416 -0
  76. package/docs/upgrade-v3.md +205 -314
  77. package/package.json +8 -8
@@ -107,6 +107,20 @@ the arm names in `arm` and `split`. The callbacks forward each impression and
107
107
  choice to `window.dataLayer`; see
108
108
  [send the events to your own analytics](#send-the-events-to-your-own-analytics-too).
109
109
 
110
+ Your flag provider returns a plain string. To check it before passing it as
111
+ `arm`, type it with `ExperimentArmName` from `c15t`, which is `control` plus
112
+ the experiment's arm names:
113
+
114
+ ```ts
115
+ import type { ExperimentArmName } from 'c15t';
116
+
117
+ // 'control' | 'wall'
118
+ type BannerArm = ExperimentArmName<typeof bannerExperiment>;
119
+
120
+ const armFromFlag = (value: string | undefined): BannerArm | undefined =>
121
+ value === 'control' || value === 'wall' ? value : undefined;
122
+ ```
123
+
110
124
  Omit `arm` to let c15t pick, and set `split` to weight the arms:
111
125
 
112
126
  ```ts
@@ -137,6 +151,14 @@ no effect; remount the provider (a `key` in React) to switch experiments.
137
151
  Assignment and arm validation load as a separate chunk, only on pages that
138
152
  set `experiment`. A site without an experiment does not download them.
139
153
 
154
+ An experiment compares choices, so the banner has to ask about at least one
155
+ category. Under a rule with `scopeMode: 'permissive'`, the banner asks only
156
+ about the categories your site declares through `consentCategories`,
157
+ `scripts` or `vendors`. With none declared, it asks about none: accepting or rejecting records a notice
158
+ acknowledgement instead of a choice, `onChoiceRecorded` never fires, and the
159
+ experiment counts impressions without choices. Outside production, c15t logs
160
+ a warning the first time the banner shows an arm in that state.
161
+
140
162
  ### Astro
141
163
 
142
164
  The Astro banner is server-rendered HTML that the browser only shows or
@@ -229,8 +251,7 @@ keeps their arm when you change the weights.
229
251
  Resolve the flag where consent resolves and pass the experiment to
230
252
  `resolveConsent`. The server reports the arm with `/init`, and the returned
231
253
  state carries the experiment to `ConsentRoot`, so the client needs no
232
- `experiment` option of its own. The Next.js example does this in the root
233
- layout of its `/experiment` route:
254
+ `experiment` option of its own. In Next.js, do this in the root layout:
234
255
 
235
256
  ```tsx title="app/layout.tsx"
236
257
  import { resolveConsent } from 'c15t/next/server';
@@ -268,8 +289,8 @@ const RootLayout = ({ children }: { children: ReactNode }) => (
268
289
  export default RootLayout;
269
290
  ```
270
291
 
271
- In the example, `lib/flags.ts` stands in for the flag above and reads the arm
272
- from the URL. `ExperimentConsent` is the `Consent` wrapper from the
292
+ `@/lib/flags` exports the flag above. `ExperimentConsent` is the `Consent`
293
+ wrapper from the
273
294
  [App Router guide](https://c15t.com/docs/frameworks/next/app-router) with the event callbacks
274
295
  from [send the events to your own analytics](#send-the-events-to-your-own-analytics-too)
275
296
  in `options`. The layout awaits consent inside `<Suspense>`, as in
@@ -622,7 +643,7 @@ arm by the visitors whose reports carry that arm.
622
643
 
623
644
  ## Try it
624
645
 
625
- Every example app under `examples/` runs this experiment outside the pages
646
+ Every example app under `internals/fixtures/` runs this experiment outside the pages
626
647
  the docs publish. Each page shows the assigned arm and the events the
627
648
  callbacks sent. Adding `arm=wall` sets the arm the way a flag would, so the
628
649
  page shows `assignedBy: host`. Run each command in the example's directory.
@@ -11,8 +11,9 @@ group: guides
11
11
  Most problems show up in one of these places. Run them before changing code.
12
12
 
13
13
  1. **Which c15t is running.** In the browser console, run `window.c15t`. v3
14
- prints `{ version, pkg, mode }`, for example `mode: 'manifest'` from
15
- `@c15t/nextjs`. `undefined` means no c15t provider has mounted on this
14
+ prints `{ version, pkg, mode, hosting }`, for example `mode: 'manifest'`
15
+ from `@c15t/nextjs`. `hosting` is `'inth'` or `'self-hosted'` once the
16
+ backend has answered `/init`, and `null` before then or without a backend. `undefined` means no c15t provider has mounted on this
16
17
  page. A `window.c15tStore` object means the page runs v2. With the script
17
18
  tag, `window.c15t` is the full browser API instead.
18
19
  2. **The backend request.** In DevTools Network, filter by your backend URL.
@@ -47,33 +47,34 @@ kernel to control its DOM lifecycle. Embeds do not require `@c15t/integrations`.
47
47
 
48
48
  ## Analytics
49
49
 
50
- | Integration | Helper | Category | Loading behavior |
51
- | --------------------------------------------------------- | ------------------------ | ---------------------------- | ---------------------------------------------------------------------------- |
52
- | [Google Tag](./google-tag.md) | `gtag` | `measurement` or `marketing` | Always loads; signals Google consent |
53
- | [Ahrefs Analytics](./ahrefs-analytics.md) | `ahrefsAnalytics` | `measurement` | Waits for effective permission |
54
- | [Adobe Analytics](./adobe-analytics.md) | `adobeAnalytics` | `measurement` | Waits for effective permission |
55
- | [Amplitude](./amplitude.md) | `amplitude` | `measurement` | Waits for effective permission; opts the SDK out on revocation |
56
- | [Cloudflare Web Analytics](./cloudflare-web-analytics.md) | `cloudflareWebAnalytics` | `measurement` | Waits for effective permission |
57
- | [Clearbit](./clearbit.md) | `clearbit` | `marketing` | Waits for effective permission |
58
- | [Microsoft Clarity](./microsoft-clarity.md) | `clarity` | `measurement` | Gated initially; keeps the SDK and signals storage consent |
59
- | [Databuddy](./databuddy.md) | `databuddy` | `measurement` | Always loads; switches the SDK's disabled flag |
60
- | [Fathom Analytics](./fathom-analytics.md) | `fathomAnalytics` | `measurement` | Waits for effective permission |
61
- | [Heap](./heap.md) | `heap` | `measurement` | Waits for effective permission |
62
- | [Matomo Analytics](./matomo-analytics.md) | `matomoAnalytics` | `measurement` | Gated by default; optional consent mode |
63
- | [Mixpanel](./mixpanel-analytics.md) | `mixpanelAnalytics` | `measurement` | Always loads; calls opt-in and opt-out APIs |
64
- | [OneDollarStats](./one-dollar-stats.md) | `oneDollarStats` | `measurement` | Waits for effective permission |
65
- | [Hotjar](./hotjar.md) | `hotjar` | `measurement` | Waits for effective permission |
66
- | [Hightouch](./hightouch.md) | `hightouch` | `measurement` | Waits for effective permission |
67
- | [LogRocket](./logrocket.md) | `logRocket` | `measurement` | Waits for effective permission |
68
- | [Plausible Analytics](./plausible-analytics.md) | `plausibleAnalytics` | `measurement` | Waits for effective permission |
69
- | [PostHog](./posthog.md) | `posthog` | `measurement` | Configurable; defaults to always loading and calling opt-in and opt-out APIs |
70
- | [Promptwatch](./promptwatch.md) | `promptwatch` | `measurement` | Waits for effective permission |
71
- | [Pirsch](./pirsch.md) | `pirsch` | `measurement` | Waits for effective permission |
72
- | [RudderStack](./rudderstack.md) | `rudderstack` | `measurement` | Gated by default; optional destination consent mode |
73
- | [Segment](./segment.md) | `segment` | `measurement` | Waits for effective permission |
74
- | [Rybbit Analytics](./rybbit-analytics.md) | `rybbitAnalytics` | `measurement` | Waits for effective permission |
75
- | [Umami Analytics](./umami-analytics.md) | `umamiAnalytics` | `measurement` | Waits for effective permission |
76
- | [Vercel Analytics](./vercel-analytics.md) | `vercelAnalytics` | `measurement` | Waits for effective permission |
50
+ | Integration | Helper | Category | Loading behavior |
51
+ | --------------------------------------------------------- | ------------------------ | ----------------------------------------------- | ---------------------------------------------------------------------------- |
52
+ | [Google Tag](./google-tag.md) | `gtag` | `measurement` or `marketing` | Always loads; signals Google consent |
53
+ | [Ahrefs Analytics](./ahrefs-analytics.md) | `ahrefsAnalytics` | `measurement` | Waits for effective permission |
54
+ | [Adobe Analytics](./adobe-analytics.md) | `adobeAnalytics` | `measurement` | Waits for effective permission |
55
+ | [Amplitude](./amplitude.md) | `amplitude` | `measurement` | Waits for effective permission; opts the SDK out on revocation |
56
+ | [Cloudflare Web Analytics](./cloudflare-web-analytics.md) | `cloudflareWebAnalytics` | `measurement` | Waits for effective permission |
57
+ | [Clearbit](./clearbit.md) | `clearbit` | `marketing` | Waits for effective permission |
58
+ | [Microsoft Clarity](./microsoft-clarity.md) | `clarity` | `measurement` | Gated initially; keeps the SDK and signals storage consent |
59
+ | [Databuddy](./databuddy.md) | `databuddy` | `measurement` | Always loads; switches the SDK's disabled flag |
60
+ | [Fathom Analytics](./fathom-analytics.md) | `fathomAnalytics` | `measurement` | Waits for effective permission |
61
+ | [Heap](./heap.md) | `heap` | `measurement` | Waits for effective permission |
62
+ | [Matomo Analytics](./matomo-analytics.md) | `matomoAnalytics` | `measurement` | Gated by default; optional consent mode |
63
+ | [Mixpanel](./mixpanel-analytics.md) | `mixpanelAnalytics` | `measurement` | Always loads; calls opt-in and opt-out APIs |
64
+ | [OneDollarStats](./one-dollar-stats.md) | `oneDollarStats` | `measurement` | Waits for effective permission |
65
+ | [Hotjar](./hotjar.md) | `hotjar` | `measurement` | Waits for effective permission |
66
+ | [Hightouch](./hightouch.md) | `hightouch` | `measurement` | Waits for effective permission |
67
+ | [LogRocket](./logrocket.md) | `logRocket` | `measurement` | Waits for effective permission |
68
+ | [Plausible Analytics](./plausible-analytics.md) | `plausibleAnalytics` | `measurement` | Waits for effective permission |
69
+ | [PostHog](./posthog.md) | `posthog` | `measurement` | Configurable; defaults to always loading and calling opt-in and opt-out APIs |
70
+ | [Promptwatch](./promptwatch.md) | `promptwatch` | `measurement` | Waits for effective permission |
71
+ | [Pirsch](./pirsch.md) | `pirsch` | `measurement` | Waits for effective permission |
72
+ | [RudderStack](./rudderstack.md) | `rudderstack` | `measurement` | Gated by default; optional destination consent mode |
73
+ | [Segment](./segment.md) | `segment` | `measurement` | Waits for effective permission |
74
+ | [Sentry](./sentry.md) | `sentry` | `necessary`; Replay and user data `measurement` | Configurable; defaults to always loading and loads Replay after permission |
75
+ | [Rybbit Analytics](./rybbit-analytics.md) | `rybbitAnalytics` | `measurement` | Waits for effective permission |
76
+ | [Umami Analytics](./umami-analytics.md) | `umamiAnalytics` | `measurement` | Waits for effective permission |
77
+ | [Vercel Analytics](./vercel-analytics.md) | `vercelAnalytics` | `measurement` | Waits for effective permission |
77
78
 
78
79
  ## Chat and support
79
80
 
@@ -0,0 +1,416 @@
1
+ ---
2
+ title: Sentry
3
+ description: Configure Sentry's CDN or your own SDK with consent for error
4
+ monitoring, Session Replay and user data.
5
+ icon: sentry
6
+ group: integrations
7
+ ---
8
+
9
+ ## Configure Sentry
10
+
11
+ Remove any existing Sentry Loader Script or CDN tags, then add this configuration.
12
+ Errors run before consent by default; Replay and SDK data collection wait for
13
+ `measurement`.
14
+
15
+ | Package manager | Command |
16
+ | :-------------- | :------------------------------------- |
17
+ | npm | `npm install @c15t/integrations@alpha` |
18
+ | pnpm | `pnpm add @c15t/integrations@alpha` |
19
+ | yarn | `yarn add @c15t/integrations@alpha` |
20
+ | bun | `bun add @c15t/integrations@alpha` |
21
+
22
+ ```ts title="src/consent-scripts.ts"
23
+ import { sentry } from '@c15t/integrations/sentry';
24
+
25
+ export const scripts = [
26
+ sentry({
27
+ dsn: 'https://your-key@o0.ingest.sentry.io/0',
28
+ initOptions: {
29
+ release: 'my-app@1.0.0',
30
+ replaysOnErrorSampleRate: 1,
31
+ replaysSessionSampleRate: 0.1,
32
+ tracesSampleRate: 0.1,
33
+ },
34
+ }),
35
+ ];
36
+ ```
37
+
38
+ The default SDK is 11.4.0 with subresource integrity: `bundle.min.js`, or
39
+ `bundle.tracing.min.js` when traces are sampled. `replay.min.js` loads after
40
+ consent when a Replay sample rate is above 0.
41
+
42
+ ## Register the scripts
43
+
44
+ Complete your [framework quickstart](https://c15t.com/docs/frameworks) first. Keep its Inth
45
+ endpoint, policy, styles and consent UI. Remove the vendor's original script,
46
+ SDK initializer or tag-manager entry, so the vendor loads only through c15t.
47
+
48
+ The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
49
+ quickstart already created a scripts file, such as `lib/scripts.ts` in the
50
+ Next.js guide, add the helper to that array instead of creating a second file.
51
+ The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
52
+ point for your framework below. These are edits to that provider, not a second
53
+ provider.
54
+
55
+ **Next.js**
56
+
57
+ Import the configuration into the client boundary from your router guide:
58
+
59
+ ```ts
60
+ import { ConsentRoot } from 'c15t/next';
61
+ import { scripts } from './consent-scripts';
62
+ ```
63
+
64
+ Keep the server-resolved `state` and shared `consentConfig` from your
65
+ router guide. Its manifest, init and save URLs stay in effect. Add
66
+ `scripts` as a top-level prop on the existing root:
67
+
68
+ ```tsx
69
+ <ConsentRoot state={state} config={consentConfig} scripts={scripts}>
70
+ {children}
71
+ </ConsentRoot>
72
+ ```
73
+
74
+ App Router, Pages Router and static export all use this `ConsentRoot` in
75
+ the `'use client'` wrapper `components/consent.tsx`. Keep `scripts` there,
76
+ because a Server Component cannot pass script callbacks to it. See
77
+ [Next.js scripts and embeds](https://c15t.com/docs/frameworks/next/scripts).
78
+
79
+ **TanStack Start**
80
+
81
+ Import the configuration into your root route and pass it to the existing
82
+ `ConsentRoot` as a top-level prop. Keep the loader, `backendURL` and
83
+ `initRoute` from the [TanStack Start quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart):
84
+
85
+ ```tsx title="src/routes/__root.tsx"
86
+ import { scripts } from '../consent-scripts';
87
+
88
+ <ConsentRoot
89
+ state={consent}
90
+ backendURL={backendURL}
91
+ initRoute={false}
92
+ scripts={scripts}
93
+ >
94
+ ```
95
+
96
+ Import vendor helpers in the root route module, not in a server function.
97
+ A server function's return value must be serializable, and script
98
+ configurations carry callbacks. See
99
+ [TanStack Start scripts](https://c15t.com/docs/frameworks/tanstack-start/scripts).
100
+
101
+ **React**
102
+
103
+ Add the configuration to the existing `ConsentProvider` options, next to
104
+ `mode`:
105
+
106
+ ```tsx title="src/consent.tsx"
107
+ import { scripts } from './consent-scripts';
108
+
109
+ <ConsentProvider options={{ mode, scripts }}>
110
+ ```
111
+
112
+ `mode` is the `hosted({ url: 'https://your-project.inth.app' })` value
113
+ from the [React quickstart](../frameworks/react/quickstart.md). Keep the banner,
114
+ dialog and preferences link inside the provider. See
115
+ [React scripts and embeds](../frameworks/react/scripts.md).
116
+
117
+ **Nuxt**
118
+
119
+ Register the scripts under the `c15t` key in `app/app.config.ts`. Adjust the
120
+ relative import to where you created `consent-scripts.ts`:
121
+
122
+ ```ts title="app/app.config.ts"
123
+ import { scripts } from '../src/consent-scripts';
124
+
125
+ export default defineAppConfig({
126
+ c15t: { scripts },
127
+ });
128
+ ```
129
+
130
+ The Nuxt module merges this over its options in `nuxt.config.ts` and starts
131
+ one script loader in the browser after hydration, once it has applied the
132
+ visitor's stored choice and privacy signals. Keep `scripts` out of
133
+ `nuxt.config.ts`, which reaches the browser as JSON and drops the vendor
134
+ callbacks. Write the vendor IDs into `consent-scripts.ts`. See
135
+ [Nuxt scripts and embeds](https://c15t.com/docs/frameworks/nuxt/scripts).
136
+
137
+ **Vue**
138
+
139
+ Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:
140
+
141
+ ```ts title="src/main.ts"
142
+ import { scripts } from './consent-scripts';
143
+
144
+ app.use(c15tVue, {
145
+ backendURL: 'https://your-project.inth.app',
146
+ scripts,
147
+ });
148
+ ```
149
+
150
+ Keep your existing backend URL and other options. The plugin starts one
151
+ script loader when the app mounts, after it has applied the visitor's stored
152
+ choice. Do not also call `createScriptLoader` from a component. See
153
+ [Vue scripts and embeds](https://c15t.com/docs/frameworks/vue/scripts).
154
+
155
+ **Astro**
156
+
157
+ Add the scripts to the client entrypoint from the
158
+ [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), the module that the
159
+ integration's `clientEntrypoint` option names. Keep `mode`, `ui` and the
160
+ framework integration in `astro.config.mjs` as they are. If the module
161
+ already exports scripts, combine the two arrays.
162
+
163
+ ```ts title="src/consent-client.ts"
164
+ import type { C15tClientOptionsExtension } from 'c15t/astro';
165
+ import { scripts } from './consent-scripts';
166
+
167
+ export default { scripts } satisfies C15tClientOptionsExtension;
168
+ ```
169
+
170
+ Vendor helpers contain callbacks, and the integration options in
171
+ `astro.config.mjs` are serialized into the page, so do not put helpers in
172
+ the integration's `scripts` option. The integration passes the client
173
+ entrypoint to the one runtime every page shares, including across
174
+ `ClientRouter` navigation.
175
+
176
+ **Svelte**
177
+
178
+ Import the scripts in the component that owns your existing provider and
179
+ pass them as a top-level prop:
180
+
181
+ ```svelte title="src/App.svelte"
182
+ <script lang="ts">
183
+ import { ConsentManagerProvider, hosted } from '@c15t/svelte';
184
+ import { scripts } from './consent-scripts';
185
+
186
+ const mode = hosted({ url: 'https://your-project.inth.app' });
187
+ </script>
188
+
189
+ <ConsentManagerProvider {mode} {scripts}>
190
+ <!-- Keep your application, consent UI and preferences link here. -->
191
+ </ConsentManagerProvider>
192
+ ```
193
+
194
+ Retain the styles and consent UI from the [Svelte quickstart](https://c15t.com/docs/frameworks/svelte/quickstart).
195
+ The provider owns the loader and disposes it on unmount.
196
+
197
+ **SvelteKit**
198
+
199
+ Add the scripts to the existing root layout provider. Keep the server load
200
+ and its serializable prefetch data from the [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart).
201
+
202
+ ```svelte title="src/routes/+layout.svelte"
203
+ <script lang="ts">
204
+ import { ConsentManagerProvider, hosted } from '@c15t/svelte';
205
+ import { scripts } from '../consent-scripts';
206
+
207
+ let { children, data } = $props();
208
+ const mode = hosted({ url: 'https://your-project.inth.app' });
209
+ </script>
210
+
211
+ <ConsentManagerProvider {mode} {scripts} prefetch={data.prefetch}>
212
+ {@render children()}
213
+ <!-- Keep your consent UI and preferences link here. -->
214
+ </ConsentManagerProvider>
215
+ ```
216
+
217
+ Import vendor helpers in the layout component, not in `+layout.server.ts`:
218
+ a server load cannot send functions to the browser. Prerendered, static and
219
+ SPA-mode pages use the same `scripts` prop. If you pass an externally owned
220
+ `runtime` to the provider, register scripts when creating that runtime instead.
221
+
222
+ **HTML**
223
+
224
+ The helpers in `@c15t/integrations` are ES modules that need a bundler. On a
225
+ page that loads the c15t script tag, paste the vendor's own snippet instead
226
+ and keep it inert until its category is allowed:
227
+
228
+ ```html
229
+ <script type="text/plain" data-c15t-category="measurement">
230
+ // The vendor's snippet, unchanged
231
+ </script>
232
+ ```
233
+
234
+ Use the category this guide names for the vendor. c15t runs the snippet
235
+ once that category is allowed, and reloads the page when the visitor
236
+ withdraws it. Helper options on this page, such as `loadMode`, do not apply
237
+ to a pasted snippet. See [HTML scripts](https://c15t.com/docs/frameworks/html/scripts).
238
+
239
+ **JavaScript**
240
+
241
+ Pass the scripts to `init()` from `@c15t/browser`, next to your backend
242
+ URL:
243
+
244
+ ```ts
245
+ import { init } from '@c15t/browser';
246
+ import { scripts } from './consent-scripts';
247
+
248
+ const consent = init({
249
+ backendURL: 'https://your-project.inth.app',
250
+ scripts,
251
+ });
252
+ ```
253
+
254
+ Keep the backend URL from your quickstart. With
255
+ `createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
256
+ A kernel you create yourself needs a loader from
257
+ `c15t/modules/script-loader`. Attach one loader per kernel. See
258
+ [JavaScript scripts](https://c15t.com/docs/frameworks/javascript/scripts).
259
+
260
+ ## Use your own Sentry SDK
261
+
262
+ For `@sentry/*` 10.67.0 or later, pass your SDK's functions instead of `dsn`.
263
+ Keep Replay in a separate module so it can load after consent:
264
+
265
+ ```ts title="src/sentry-replay.ts"
266
+ import { replayIntegration } from '@sentry/browser';
267
+
268
+ export const createReplay = () =>
269
+ replayIntegration({ maskAllText: true, blockAllMedia: true });
270
+ ```
271
+
272
+ In `src/consent-scripts.ts`, replace the CDN configuration with:
273
+
274
+ ```ts
275
+ import { getClient, init, setUser } from '@sentry/browser';
276
+
277
+ sentry({
278
+ getClient,
279
+ init,
280
+ setUser,
281
+ initOptions: {
282
+ dsn: 'https://your-key@o0.ingest.sentry.io/0',
283
+ replaysSessionSampleRate: 0.1,
284
+ replaysOnErrorSampleRate: 1,
285
+ },
286
+ replay: {
287
+ load: () => import('./sentry-replay').then((m) => m.createReplay()),
288
+ },
289
+ });
290
+ ```
291
+
292
+ Passing `init` protects startup captures before other SDK integrations run.
293
+ Custom initializers must forward the supplied options to `Sentry.init`, including
294
+ after a dynamic import. Avoid initialization as an import side effect.
295
+
296
+ Use named imports from the same Sentry package throughout your app. Next.js and
297
+ Vite split dynamically imported Replay into its own chunk; esbuild and Bun keep
298
+ it in the main bundle. A main-bundle `Sentry.replayIntegration` reference through
299
+ `import * as Sentry` also prevents the split.
300
+
301
+ ### If Sentry already initializes independently
302
+
303
+ c15t applies filters once it finds the client; it cannot retract earlier data.
304
+ For startup protection, disable collection and remove event users in your own
305
+ `Sentry.init` call. With SDK 10.67, omit `queues`:
306
+
307
+ ```ts
308
+ // Include these options in your app's Sentry.init call.
309
+ maxBreadcrumbs: 0,
310
+ dataCollection: {
311
+ userInfo: false,
312
+ cookies: false,
313
+ httpHeaders: { request: false, response: false },
314
+ httpBodies: [],
315
+ urlQueryParams: false,
316
+ graphQL: { document: false, variables: false },
317
+ genAI: { inputs: false, outputs: false },
318
+ databaseQueryData: false,
319
+ queues: false,
320
+ stackFrameVariables: false,
321
+ frameContextLines: 0,
322
+ },
323
+ beforeSend(event) {
324
+ delete event.user;
325
+ delete event.request;
326
+ return event;
327
+ },
328
+ ```
329
+
330
+ These settings keep collection and event users disabled after consent too.
331
+ To restore event users, check effective c15t permission in `beforeSend`. Wait
332
+ until the adapter is registered before assigning scope users or capturing logs
333
+ and metrics.
334
+
335
+ ## Options
336
+
337
+ | Option | Default | Behavior |
338
+ | ----------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
339
+ | `dsn` | Required to load Sentry | Your project's DSN. c15t loads the SDK and calls `Sentry.init`. |
340
+ | `initOptions` | `{}` | Passed to `Sentry.init` with c15t's consent integration first. CDN `integrations` callbacks can access `window.Sentry`. |
341
+ | `version` | `'11.4.0'` | SDK version to load. Another version loads without subresource integrity. |
342
+ | `tracing` | `true` when traces are sampled | Loads the tracing bundle and adds `browserTracingIntegration()`. |
343
+ | `replay` | Loads CDN Replay when sampled | CDN: `{ category, options }` or `false`. SDK: `{ category, load }`. |
344
+ | `getClient` | Required for your own SDK | Your SDK's `getClient`. Supports initialization after c15t starts. |
345
+ | `setUser` | None | `setUser` from your Sentry SDK. Called with `null` while user data is not allowed. Required with `pii.user`. |
346
+ | `init` | None | Your SDK's `init`, called once when `loadMode` allows. Required for SDK mode with `'after-consent'`. |
347
+ | `replay.load` | None | With your own SDK, returns a new `replayIntegration()`. Called once, the first time Replay is allowed. |
348
+ | `loadMode` | `'always'` | When error monitoring runs. See the table below. |
349
+ | `replay.category` | `'measurement'` | Consent condition for Replay. `pii.category` must also be allowed. |
350
+ | `pii.category` | `'measurement'` | Consent condition for SDK data collection and user data. `'necessary'` permits it while the Sentry vendor is enabled. |
351
+ | `pii.user` | None | Returns the user to set when user data becomes allowed. |
352
+ | `onError` | Report to an available Sentry client | Receives CDN and Replay lifecycle failures. Set it to report CDN download failures before Sentry exists. |
353
+
354
+ Replay's category is registered only with an SDK `replay.load` function or
355
+ enabled CDN Replay with a positive sample rate.
356
+
357
+ ## Loading and revocation
358
+
359
+ | `loadMode` | When Sentry runs | On withdrawal without a reload |
360
+ | ----------------- | ----------------------------------- | ----------------------------------------------------------- |
361
+ | `'always'` | On every page, before consent | Replay stops; errors keep reaching Sentry without user data |
362
+ | `'after-consent'` | Only while `measurement` is allowed | Sentry stops sending anything |
363
+
364
+ Replay needs both `replay.category` and `pii.category`, because recordings
365
+ include page URLs with queries and fragments. Keep Replay out of `Sentry.init`:
366
+ `replaysOnErrorSampleRate` can buffer recordings before consent. On denial or
367
+ final removal, c15t stops Replay without flushing, discards queued uploads and
368
+ blocks restarts. Data already sent stays in Sentry; a later grant preserves
369
+ the original sampling decision.
370
+
371
+ While PII is denied, c15t disables supported `dataCollection` settings and IP
372
+ inference. Before sending, it removes user fields, feedback contact details,
373
+ visitor identifiers in sessions, request data and URL queries/fragments from
374
+ supported events, spans, logs, metrics and breadcrumbs, including retained
375
+ breadcrumbs. A grant restores your resolved collection settings and allowlists.
376
+ Tracing itself is not gated. Custom messages, tags, extras, attachments and
377
+ URL paths remain diagnostics, so keep sensitive values out of them.
378
+
379
+ The vendor slug is `sentry`. Turning it off denies Replay and PII, plus errors
380
+ in `'after-consent'` mode. See [vendor consent](./overview.md#vendor-switches-and-cookie-cleanup).
381
+
382
+ ### Shared clients and configuration changes
383
+
384
+ Keep callback functions stable across renders. Equal options and callbacks
385
+ reuse the SDK bundle and active recording. Changed CDN options initialize a
386
+ client with the new DSN and settings, while reusing the page's single Replay
387
+ recorder. Its original Replay options and sampling decision last until reload.
388
+
389
+ Every active configuration targeting one client must allow each feature;
390
+ removing a loader releases its restriction and preserves surviving loaders and
391
+ downloads. Final removal denies PII and stops Replay, and also disables errors
392
+ in `'after-consent'` mode. An app-initiated `Sentry.close()` stays closed after
393
+ a grant. Separate clients have separate permissions.
394
+
395
+ ## Content security policy
396
+
397
+ Allow `worker-src blob:` for Replay and your ingest or tunnel endpoint in
398
+ `connect-src`. CDN mode also needs `https://browser.sentry-cdn.com` in
399
+ `script-src`. The loader or provider's `nonce` applies to both CDN bundles;
400
+ a per-script nonce overrides it.
401
+
402
+ ## Verify Sentry
403
+
404
+ Test in a private window with an opt-in policy and
405
+ `replaysSessionSampleRate: 1`. Filter DevTools Network by `sentry`.
406
+
407
+ 1. Load the page. `bundle.min.js` loads and errors reach Sentry, without `user`.
408
+ No `replay.min.js` or `replay_recording` request is sent. With
409
+ `loadMode: 'after-consent'`, nothing loads.
410
+ 2. Allow measurement. Without a reload, Replay loads and `replay_recording`
411
+ requests start.
412
+ 3. Turn measurement off and save. c15t reloads the page, and no replay request
413
+ follows.
414
+
415
+ See the [consent verification guide](../guides/verify-consent.md) for
416
+ navigation, expiry and hosting checks.