@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
@@ -1,11 +1,29 @@
1
1
  ---
2
2
  title: Migrate to v3
3
- description: Upgrade a c15t v2 app to v3. Covers packages, the Next.js and React
4
- providers, the JavaScript runtime, custom UI built on useConsentManager,
5
- callbacks, policies, stored consent and a self-hosted backend.
3
+ description: Upgrade from c15t v2 to v3. Pick the guide for the v2 package you
4
+ use (@c15t/nextjs, @c15t/react or the c15t store), rename @c15t/scripts, keep
5
+ visitors' choices, and upgrade a self-hosted backend and the Node.js SDK.
6
6
  group: reference
7
7
  ---
8
8
 
9
+ ## Hand the upgrade to a coding agent
10
+
11
+ **Upgrade this app to c15t v3**
12
+
13
+ Paste into Claude Code, Codex, Cursor or another coding agent at the root of your app.
14
+
15
+ ```prompt
16
+ Upgrade this app from c15t v2 to c15t v3.
17
+
18
+ 1. Check `package.json` for c15t packages. `@c15t/nextjs` means Next.js, `@c15t/react` without it means React, and `c15t` alone means the JavaScript store.
19
+ 2. Read the matching guide before you change anything: <https://c15t.com/docs/frameworks/next/upgrade-v3.md>, <https://c15t.com/docs/frameworks/react/upgrade-v3.md> or <https://c15t.com/docs/frameworks/javascript/upgrade-v3.md>. Follow it in order and do not guess v3 APIs from memory.
20
+ 3. If the app also runs its own c15t backend with `@c15t/backend`, read <https://c15t.com/docs/upgrade-v3.md> and upgrade the backend in the same change. Start with `npx @c15t/cli@alpha codemods backend-config-to-v3 node-sdk-to-v3 --dry-run --json`, then run it without `--dry-run`.
21
+ 4. Keep the existing backend URL.
22
+ 5. Run the typecheck and build when you finish.
23
+
24
+ List the files you changed, anything you could not migrate, and how you checked that vendor requests still wait for consent.
25
+ ```
26
+
9
27
  ## What changes in v3
10
28
 
11
29
  * One package, `c15t`, replaces `@c15t/nextjs`, `@c15t/react` and the v2
@@ -23,57 +41,38 @@ group: reference
23
41
  rewrites most calls.
24
42
  * Callbacks have new names, and each reports either a recorded choice or a
25
43
  permission change.
44
+ * `theme` tokens no longer generate CSS on their own. Render `ConsentTheme` or
45
+ add `generateThemeCSS()` output to your stylesheet.
46
+ * IAB moves from the `iab` provider option to an `IABProvider` component in
47
+ React and Next.js.
48
+ * Every package ships ESM only. `require()` of a c15t package no longer
49
+ resolves.
26
50
  * Visitors' existing choices keep working. v3 reads the v2 cookie.
27
51
  * A self-hosted backend takes `database` instead of `adapter`, and needs a
28
52
  schema migration.
29
53
  * `@c15t/node-sdk` has a new client, `createC15tClient()`. Its methods return
30
54
  results instead of throwing, and it reads no environment variables.
31
55
 
32
- ## Upgrade in this order
33
-
34
- 1. Upgrade React and React DOM to 18 or newer. The v3 React and Next.js
35
- adapters require them, and `c15t/next` requires Next.js 15 or 16.
36
- 2. [Replace the packages](#replace-the-packages), including the
37
- [integrations dependency](#rename-the-integrations-dependency).
38
- 3. Run the [`useConsentManager()` codemod](#replace-useconsentmanager) if your
39
- code calls it.
40
- 4. Replace the provider for your framework: [Next.js](#nextjs-with-consentmanagerprovider),
41
- [React](#react-with-consentmanagerprovider) or [JavaScript](#javascript-with-getorcreateconsentruntime).
42
- 5. Update [callbacks](#replace-callbacks), [policies](#move-policy-packs-to-policy-rules)
43
- and [other options](#rename-options-and-components).
44
- 6. If you self-host, [upgrade the backend](#upgrade-a-self-hosted-backend) in the
45
- same release as the clients.
46
- 7. If server code calls the API, [update the Node.js SDK](#update-the-nodejs-sdk).
47
- 8. [Check the result](#check-the-migration).
48
-
49
- ## Replace the packages
50
-
51
- Remove `@c15t/nextjs` and `@c15t/react`, then install `c15t` from the `alpha`
52
- dist-tag. npm's default tag still resolves v2.
53
-
54
- | Package manager | Command |
55
- | :-------------- | :------------------------------------------------ |
56
- | npm | `npm install c15t@alpha @c15t/integrations@alpha` |
57
- | pnpm | `pnpm add c15t@alpha @c15t/integrations@alpha` |
58
- | yarn | `yarn add c15t@alpha @c15t/integrations@alpha` |
59
- | bun | `bun add c15t@alpha @c15t/integrations@alpha` |
60
-
61
- Drop `@c15t/integrations@alpha` if you do not use its vendor helpers.
62
-
63
- | v2 import | v3 import |
64
- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
65
- | `@c15t/nextjs` | `c15t/next`. Server helpers are in `c15t/next/server`, `c15t/next/pages` and `c15t/next/api`. |
66
- | `@c15t/react` | `c15t/react` |
67
- | `@c15t/react/headless` | `c15t/react/headless` |
68
- | `@c15t/nextjs/styles.css` | `c15t/next/styles.css` |
69
- | `@c15t/react/styles.css` | `c15t/react/styles.css` |
70
- | `c15t` (`getOrCreateConsentRuntime` and the store) | `c15t` for the engine and `c15t/runtime` for a managed runtime. The APIs are new; see [JavaScript](#javascript-with-getorcreateconsentruntime). |
71
- | `DevTools` from `@c15t/dev-tools/react` | `DevTools` from `c15t/react/devtools` or `c15t/next/devtools` |
72
- | `@c15t/scripts/*` | `@c15t/integrations/*`, with unchanged helper names. See [rename the integrations dependency](#rename-the-integrations-dependency). |
73
-
74
- The scoped packages `@c15t/nextjs`, `@c15t/react` and `@c15t/core` still exist
75
- at v3, so imports from them keep working if you pin them to `@alpha`. The docs
76
- use the `c15t` subpaths.
56
+ ## Pick your guide
57
+
58
+ Each guide covers one v2 package from start to finish.
59
+
60
+ | You use in v2 | Follow |
61
+ | ----------------------------------------------------- | -------------------------------------------------------------------- |
62
+ | `@c15t/nextjs` | [Next.js](https://c15t.com/docs/frameworks/next/upgrade-v3) |
63
+ | `@c15t/react` | [React](./frameworks/react/upgrade-v3.md) |
64
+ | `c15t`, with `getOrCreateConsentRuntime` or the store | [JavaScript](https://c15t.com/docs/frameworks/javascript/upgrade-v3) |
65
+
66
+ v3 also adds TanStack Start, Nuxt, Vue, Astro, Svelte, SvelteKit and a script
67
+ tag. If you want to move to one of them, start with its quickstart from
68
+ [Choose your setup](./concepts/choose-your-setup.md).
69
+
70
+ If you run your own c15t backend, upgrade it in the same release as the
71
+ clients, following [Upgrade a self-hosted backend](#upgrade-a-self-hosted-backend).
72
+ Server code that calls the API with `@c15t/node-sdk` follows
73
+ [Update the Node.js SDK](#update-the-nodejs-sdk).
74
+
75
+ The sections below apply to every app.
77
76
 
78
77
  ## Rename the integrations dependency
79
78
 
@@ -91,21 +90,29 @@ dist-tag:
91
90
  Replace the package name in each import. Vendor subpaths and helper names stay
92
91
  the same.
93
92
 
94
- Before, in v2 and earlier v3 alphas:
93
+ Before, in v2:
95
94
 
96
95
  ```ts
97
- import { createEventDispatcher } from '@c15t/scripts/events';
96
+ import { googleTagManager } from '@c15t/scripts/google-tag-manager';
98
97
  import { posthog } from '@c15t/scripts/posthog';
99
98
  ```
100
99
 
101
100
  After:
102
101
 
103
102
  ```ts
104
- import { createEventDispatcher } from '@c15t/integrations/events';
103
+ import { googleTagManager } from '@c15t/integrations/google-tag-manager';
105
104
  import { posthog } from '@c15t/integrations/posthog';
106
105
  ```
107
106
 
108
107
  The `scripts` configuration option and the `Script` type keep their names.
108
+
109
+ Two helper behaviors change. A helper that needs an ID, such as `metaPixel`,
110
+ `googleTagManager` or `posthog`, now throws when the ID is empty or only
111
+ whitespace, where v2 loaded a broken script. And if your code reads
112
+ `@c15t/integrations/registry`, an entry's `consentCategory` can now be a
113
+ compound condition such as `{ and: ['marketing', 'measurement'] }`, not only a
114
+ category name.
115
+
109
116
  `@c15t/scripts` stays available as a deprecated compatibility package for all
110
117
  of v3. It re-exports the implementation and types of `@c15t/integrations`, so
111
118
  you can migrate imports separately from the rest of the upgrade. Compatibility
@@ -171,246 +178,86 @@ export function AcceptButton() {
171
178
  The codemod keeps the entry you imported from. Change `@c15t/react` to
172
179
  `c15t/react` afterwards if you moved to the umbrella package. Fields it cannot
173
180
  rewrite stay on a `useConsentManager()` call under a `TODO(c15t v3)` comment,
174
- which fails the build until you replace them. The table lists every v2 field.
181
+ which fails the build until you replace them. The table lists the v2 fields
182
+ components read. The other fields held provider configuration, such as
183
+ `legalLinks`, `scripts` and `storageConfig`, or store internals; set
184
+ configuration through `ConsentProvider` props.
175
185
  Import hooks from `c15t/react`, `c15t/next` or `c15t/tanstack-start`, and
176
186
  `useHeadlessConsentUI` from the matching `/headless` entry.
177
187
 
178
- | v2 field | v3 replacement |
179
- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
180
- | `activeUI` | `useActiveUI()`. It returns `null` until c15t picks a surface, where v2 returned `'none'`. |
181
- | `setActiveUI(ui)` | `useSetActiveUI()` |
182
- | `has(category)` | `useConsent(category)`, one call per category at the top of the component |
183
- | `consents` | `useConsents()` |
184
- | `saveConsents('all')` | `useHeadlessConsentUI().saveCustomPreferences('all')` |
185
- | `saveConsents('necessary')` | `useHeadlessConsentUI().saveCustomPreferences('none')` |
186
- | `saveConsents('custom')` | `useHeadlessConsentUI().saveCustomPreferences()`, which saves the draft |
187
- | `selectedConsents` | `useConsentDraft().values` |
188
- | `setSelectedConsent(name, value)` | `useConsentDraft().set(name, value)` |
189
- | `setConsent(name, value)` | `useSaveConsents()`, called with `{ [name]: value }` |
190
- | `consentInfo` | `useExplicitChoice()` |
191
- | `hasConsented()` | `useExplicitChoice() !== null` |
192
- | `consentCategories`, `consentTypes`, `getDisplayedConsents()` | `useConsentDraft().displayedCategories`, with labels from `useTranslations().consentTypes` |
193
- | `policyCategories` | `usePolicyCategories()`. The v2 list started with `'necessary'`; this one does not. |
194
- | `policyScopeMode` | `usePolicyScopeMode()` |
195
- | `policyBanner` | `usePromptPresentation()` |
196
- | `policyDialog` | `usePreferencesPresentation()` |
197
- | `model` | `useModel()`. It returns `null` while no policy matches, where v2 returned `'opt-in'`. |
198
- | `branding` | `useBranding()`. It returns `null` when unset, where v2 returned `'c15t'`. |
199
- | `iab` | `useIABSnapshot()` |
200
- | `locationInfo` | `useLocation()` |
201
- | `overrides`, `setOverrides()` | `useOverrides()`, `useSetOverrides()` |
202
- | `setLanguage()` | `useSetLanguage()` |
203
- | `user`, `identifyUser()` | `useUser()`, `useIdentify()` |
204
- | `translationConfig` | `useTranslations()` for the active strings |
205
- | `subscribeToConsentChanges(listener)` | `useSubscribeToConsentChanges()`, or the `onPermissionsChanged` callback |
206
- | `updateConsentCategories(categories)` | `useRegisterConsentCategories()` |
207
- | `manager` | Nothing. Use the hooks above. |
188
+ | v2 field | v3 replacement |
189
+ | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
190
+ | `activeUI` | `useActiveUI()`. It returns `null` until c15t picks a surface, where v2 returned `'none'`. |
191
+ | `setActiveUI(ui)` | `useSetActiveUI()` |
192
+ | `has(category)` | `useConsent(category)`, one call per category at the top of the component |
193
+ | `consents` | `useConsents()` |
194
+ | `saveConsents('all')` | `useHeadlessConsentUI().saveCustomPreferences('all')` |
195
+ | `saveConsents('necessary')` | `useHeadlessConsentUI().saveCustomPreferences('none')` |
196
+ | `saveConsents('custom')` | `useConsentDraft().save()`, or `useHeadlessConsentUI().saveCustomPreferences()` inside a `ConsentDraftProvider` |
197
+ | `selectedConsents` | `useConsentDraft().values` |
198
+ | `setSelectedConsent(name, value)` | `useConsentDraft().set(name, value)` |
199
+ | `setConsent(name, value)` | `useSaveConsents()`, called with `{ [name]: value }` |
200
+ | `consentInfo` | `useExplicitChoice()` |
201
+ | `hasConsented()` | `useExplicitChoice() !== null` |
202
+ | `consentCategories`, `consentTypes`, `getDisplayedConsents()` | `useConsentDraft().displayedCategories`, with labels from `useTranslations().consentTypes` |
203
+ | `policyCategories` | `usePolicyCategories()`. The v2 list started with `'necessary'`; this one does not. |
204
+ | `policyScopeMode` | `usePolicyScopeMode()` |
205
+ | `policyBanner` | `usePromptPresentation()` |
206
+ | `policyDialog` | `usePreferencesPresentation()` |
207
+ | `model` | `useModel()`. It returns `null` while no policy matches, where v2 returned `'opt-in'`. |
208
+ | `branding` | `useBranding()`. It returns `null` when unset, where v2 returned `'c15t'`. |
209
+ | `iab` | `useIABSnapshot()` |
210
+ | `locationInfo` | `useLocation()` |
211
+ | `overrides`, `setOverrides()` | `useOverrides()`, `useSetOverrides()` |
212
+ | `setLanguage()` | `useSetLanguage()` |
213
+ | `user`, `identifyUser()` | `useUser()`, `useIdentify()` |
214
+ | `translationConfig` | `useTranslations()` for the active strings |
215
+ | `subscribeToConsentChanges(listener)` | `useSubscribeToConsentChanges()`, or the `onPermissionsChanged` callback |
216
+ | `updateConsentCategories(categories)` | `useRegisterConsentCategories()` |
217
+ | `manager` | Nothing. Use the hooks above. |
208
218
 
209
219
  `saveCustomPreferences()` saves the way the stock buttons do. It closes the
210
220
  banner or dialog after a successful save. `useSaveConsents()` calls the engine
211
221
  directly and does not close anything.
212
222
 
213
223
  In v2, `useConsentManager()` kept one draft per component. In v3,
214
- `useConsentDraft()` and `useHeadlessConsentUI()` share the draft of the nearest
215
- `ConsentDraftProvider`. When one component stages choices and another saves
216
- them, render both inside the same `ConsentDraftProvider`; otherwise the save
217
- commits nothing you staged. `ConsentWidget` already includes one. The codemod
224
+ `useConsentDraft()` and `useHeadlessConsentUI()` share a draft only inside a
225
+ `ConsentDraftProvider`. Without one, `saveCustomPreferences()` saves a fresh
226
+ draft and ignores what `useConsentDraft().set()` staged, even in the same
227
+ component. Save with `useConsentDraft().save()`, or render the code that stages
228
+ and the code that saves inside one `ConsentDraftProvider`. `ConsentWidget`
229
+ already includes one. The codemod
218
230
  adds a `TODO(c15t v3)` comment where this applies.
219
231
 
220
- ## Next.js with `ConsentManagerProvider`
221
-
222
- A typical v2 App Router app wrapped the layout in a client component:
223
-
224
- ```tsx title="Before (v2): components/consent-manager/provider.tsx"
225
- 'use client';
226
-
227
- import {
228
- ConsentBanner,
229
- ConsentDialog,
230
- ConsentManagerProvider,
231
- } from '@c15t/nextjs';
232
-
233
- export default function ConsentManagerClient({ children }) {
234
- return (
235
- <ConsentManagerProvider
236
- options={{ mode: 'hosted', backendURL: 'https://your-project.c15t.dev' }}
237
- >
238
- <ConsentBanner />
239
- <ConsentDialog />
240
- {children}
241
- </ConsentManagerProvider>
242
- );
243
- }
244
- ```
245
-
246
- v3 gives you two ways forward:
247
-
248
- * **Resolve consent on the server** (recommended). The server reads the
249
- visitor's policy and stored choice, so gated scripts can start right after
250
- hydration and the banner can be in the first HTML. Replace the client
251
- component with `ConsentRoot`, pass it `resolveConsent()` from
252
- `c15t/next/server` as `state` and your `defineConsentConfig()` result as
253
- `config`, and add the manifest route. Follow the
254
- [App Router guide](https://c15t.com/docs/frameworks/next/app-router) or the
255
- [Pages Router guide](https://c15t.com/docs/frameworks/next/pages-router). This replaces v2's
256
- `fetchInitialData()`, `ssrData` and `C15tPrefetch`.
257
- * **Keep browser initialization**, as v2 did without `fetchInitialData()`. Keep
258
- your client component, import from `c15t/next`, and change the provider:
259
-
260
- ```tsx title="After (v3): components/consent-manager/provider.tsx, changed lines"
261
- import { ConsentBanner, ConsentDialog, ConsentProvider, hosted } from 'c15t/next';
262
-
263
- // In the component, replace ConsentManagerProvider:
264
- <ConsentProvider
265
- options={{ mode: hosted({ url: 'https://your-project.c15t.dev' }) }}
266
- >
267
- ```
268
-
269
- Static exports use browser initialization too. See
270
- [static export](https://c15t.com/docs/frameworks/next/static-export) and
271
- [client-side setup](https://c15t.com/docs/frameworks/next/client-side).
272
-
273
- Either way, change the stylesheet import to `c15t/next/styles.css`, and keep
274
- your backend URL. An existing hosted URL such as `https://your-project.c15t.dev`
275
- keeps working with v3 clients. New Inth projects use URLs such as
276
- `https://your-project.inth.app`. The
277
- [CLI setup command](https://c15t.com/docs/cli/quickstart) can generate the browser-initialized
278
- version for you.
279
-
280
- ## React with `ConsentManagerProvider`
281
-
282
- Swap `ConsentManagerProvider` for `ConsentProvider` from `c15t/react`, and pass
283
- a transport as `mode`:
284
-
285
- | v2 option | v3 option |
286
- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
287
- | `mode: 'hosted'` with `backendURL` | `mode: hosted({ url })` |
288
- | `mode: 'offline'` with `offlinePolicy` | `mode: offline({ policyRules })` |
289
- | `mode: 'custom'` with `endpointHandlers` | `mode: custom(transport)`, where `transport` implements the v3 transport interface. `endpointHandlers` throws. |
290
-
291
- `hosted` and `offline` are exported from `c15t/react`. The
292
- [React quickstart](./frameworks/react/quickstart.md) shows the full provider.
293
- [Data fetching](./concepts/data-fetching.md) covers custom transports.
294
-
295
- Offline mode keeps choices in the browser and records nothing on a server. Not
296
- recommended for production environments.
297
-
298
- ## JavaScript with `getOrCreateConsentRuntime`
299
-
300
- v2's `getOrCreateConsentRuntime()`, `configureConsentManager()` and the
301
- Zustand `consentStore` are gone. v3 has two replacements:
302
-
303
- * `createConsentKernel()` from `c15t`, with `createHostedTransport()`. You read
304
- `kernel.getSnapshot()`, listen with `kernel.subscribe()`, and save with
305
- `kernel.commands.save('all' | 'none' | { marketing: false })`. See the
306
- [JavaScript quickstart](https://c15t.com/docs/frameworks/javascript/quickstart).
307
- * A script tag that includes the stock banner, for sites without a build
308
- step. See [script tag](https://c15t.com/docs/frameworks/html/quickstart).
309
-
310
- v3 also adds adapters for TanStack Start, Nuxt, Vue, Astro, Svelte and
311
- SvelteKit. If you wired the v2 runtime into one of these frameworks, move to its
312
- adapter from [Choose your setup](./concepts/choose-your-setup.md).
313
-
314
- ## Replace callbacks
315
-
316
- Callbacks stay under `options.callbacks`, with new names:
317
-
318
- | v2 callback | v3 callback |
319
- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
320
- | `onConsentSet` | `onPermissionsChanged` to react to what may run now. `onChoiceRecorded` to react to the visitor's accept, reject or save. |
321
- | `onConsentChanged` | `onChoiceRecorded`. See the note below the table. |
322
- | `onBannerFetched` | No provider callback. Read `usePolicyResolution()` to know when the policy has resolved. |
323
- | `onError`, `onBeforeConsentRevocationReload` | Unchanged |
324
-
325
- v2's `onConsentChanged` fired only when a save changed at least one category's
326
- value, and passed `preferences` and `previousPreferences`. v3's
327
- `onChoiceRecorded` fires for every accept, reject or save that confirms a
328
- category, including one that saves the same values again, because each save
329
- renews the choice's confirmation time. Its payload carries `snapshot`,
330
- `confirmed` and `actionAt`. To act only on a real change, keep the last
331
- `snapshot.explicitChoice` you saw and compare it.
332
-
333
- v2's `onConsentSet` also fired on initialization and hydration. In v3,
334
- `onChoiceRecorded` runs only when the visitor acts, and `onPermissionsChanged`
335
- runs when effective permissions change for any reason, including expiry, a
336
- policy update or a privacy signal.
337
-
338
- ```tsx title="After (v3): provider options"
339
- callbacks: {
340
- onChoiceRecorded(event) {
341
- console.log('Visitor recorded a choice', event);
342
- },
343
- onPermissionsChanged(event) {
344
- console.log('Effective permissions changed', event);
345
- },
346
- },
347
- ```
348
-
349
- ## Move policy packs to policy rules
350
-
351
- v2 policy packs become policy rules, and `policyPackPresets` becomes
352
- `policyRulePresets`, exported from `c15t`. Where the rules live depends on your
353
- backend:
354
-
355
- * **Inth**: set the rules in your Inth project. Rules in client code do not
356
- change a hosted policy.
357
- * **Self-hosted**: move the backend's `policyPacks` to `manifest.policyRules`.
358
- See [policy configuration](https://c15t.com/docs/self-host/guides/policy-packs).
359
- * **Offline**: move `offlinePolicy.policyPacks` to `offline({ policyRules })`.
360
-
361
- ```tsx title="After (v3): offline policy rules"
362
- import { policyRulePresets } from 'c15t';
363
- import { offline } from 'c15t/react';
364
-
365
- const mode = offline({
366
- policyRules: [policyRulePresets.europeOptIn(), policyRulePresets.worldNone()],
367
- });
368
- ```
369
-
370
- | v2 preset | v3 preset |
371
- | ----------------------------------------- | ------------- |
372
- | `europeOptIn()`, `europeIab()` | Same names |
373
- | `californiaOptIn()`, `californiaOptOut()` | Same names |
374
- | `quebecOptIn()` | Same name |
375
- | `worldNoBanner()` | `worldNone()` |
376
-
377
- v3 adds presets for more regions. `offline()` without `policyRules` uses
378
- `recommendedPolicyRules()`. Read [policies](./concepts/policies.md) before you
379
- change a rule's model or prompt, and
380
- [how consent works](./concepts/how-consent-works.md) for the difference
381
- between a permission and a recorded choice.
382
-
383
- ## Rename options and components
384
-
385
- | v2 | v3 |
386
- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
387
- | `translations` | `i18n` |
388
- | `backendURL`, `mode: 'hosted'` | `mode: hosted({ url })` |
389
- | `ssrData` (Next.js) | `ConsentRoot` with `state` from `resolveConsent()` |
390
- | `iframeBlockerConfig` | `iframeBlocker`, or `false` to turn it off |
391
- | `Frame` | `ConsentGate`. `Frame` still works as a deprecated alias. |
392
- | `frame` translations, such as `frame.title` | `consentGate`, such as `consentGate.title`. Copy under `frame` still applies and logs a warning outside production. |
393
- | `FrameTranslations` | `ConsentGateTranslations`. `FrameTranslations` still works as a deprecated alias. |
394
- | `--frame-*` CSS variables | `--consent-gate-*`, with the same suffixes, such as `--consent-gate-placeholder-background-color`. The old names no longer apply. |
395
- | `YouTubeEmbed`, `GoogleMap` | Removed. Wrap your own embed in `ConsentGate`. |
396
- | `useConsentScript()` | Removed. Register the script in `scripts` with a `@c15t/integrations` helper. |
397
- | `useSSRStatus()` | Removed |
398
-
399
- `ConsentBanner`, `ConsentDialog`, `ConsentDialogLink`, `ConsentDialogTrigger`
400
- and `ConsentWidget` keep their names. The `scripts`, `networkBlocker`,
401
- `legalLinks`, `overrides`, `theme` and `colorScheme` options keep theirs too.
402
-
403
232
  ## Keep visitors' existing choices
404
233
 
405
234
  v3 uses the same `c15t` cookie and storage key as v2 and reads v2 records. It
406
235
  does not rewrite them on page load; the next accept, reject or save writes a
407
236
  v3 record.
408
237
 
409
- A v2 denial keeps restricting its category. A v2 grant keeps applying while it
410
- is within the policy's validity period and the policy has not changed since the
411
- visitor chose. Otherwise c15t asks again. Do not copy permissions into new
238
+ A v2 denial keeps restricting its category. A v2 grant keeps applying until
239
+ the policy's validity period runs out, and then c15t asks again. If the rule
240
+ comes from a preset and the v2 record noted the policy it was made under, c15t
241
+ also asks again when that policy has changed. Under custom rules, a v2 grant
242
+ applies until it expires. Do not copy permissions into new
412
243
  records yourself; c15t decides which v2 choices still count.
413
244
 
245
+ ## Choose the HTML bundle
246
+
247
+ For HTML script tags, `c15t.js` is now the hosted bundle. Keep that filename
248
+ for Inth or a self-hosted backend and supply `data-backend-url`, a queued
249
+ `backendURL`, or a hosted factory. Loading it without a backend no longer
250
+ starts offline mode.
251
+
252
+ If an earlier v3 script tag used local policy rules, switch its URL to
253
+ `c15t.offline.js`. For manifest mode, custom transports or a page that selects
254
+ its mode at runtime, use the root ES module `@c15t/browser`, which still
255
+ accepts every mode. The headless script supports those modes with your own
256
+ UI, and the IAB script supports them for IAB policies. The self-hosted
257
+ backend's `/c15t.js` route now resolves policies through `/init`; its headless
258
+ and IAB routes still preload the manifest. See the
259
+ [script file reference](https://c15t.com/docs/frameworks/html/attributes-and-api#script-files).
260
+
414
261
  ## Upgrade a self-hosted backend
415
262
 
416
263
  Deploy the v3 backend and the v3 clients in the same release. A v3 client
@@ -421,8 +268,9 @@ categories stay denied and no banner appears.
421
268
  2. Replace `adapter` with `database` in your config. The Drizzle, Prisma,
422
269
  TypeORM and Kysely adapters are gone; point `database` at the same SQL
423
270
  database instead. MongoDB has no migration path.
424
- 3. Move `policyPacks`, `branding`, `iab` and `appName` under `manifest`, and
425
- rename `policyPacks` to `policyRules`.
271
+ 3. Move `policyPacks`, `branding`, `customTranslations`, `i18n` and `appName`
272
+ under `manifest`, and rename `policyPacks` to `policyRules`. Split `iab`
273
+ between `manifest.iab` and `gvl`, as the table below shows.
426
274
  4. Back up the database, then plan and apply the schema migration:
427
275
 
428
276
  ```bash
@@ -437,9 +285,39 @@ nullable. [Database setup](https://c15t.com/docs/self-host/guides/database-setup
437
285
  covers the details, and the [backend quickstart](https://c15t.com/docs/self-host/quickstart)
438
286
  shows a complete v3 config and route.
439
287
 
288
+ The `backend-config-to-v3` codemod does step 3 for the keys that move as they
289
+ are, and leaves a `TODO(c15t v3)` comment on `adapter`, `iab`,
290
+ `disableGeoLocation` and the other options in the table below:
291
+
292
+ ```bash
293
+ npx @c15t/cli@alpha codemods backend-config-to-v3 policy-packs-to-policy-rules --dry-run --json
294
+ ```
295
+
296
+ Preset calls such as `policyRulePresets.europeOptIn()` need no other change.
297
+ A hand-written v2 pack uses `consent` and `ui` keys, which v3 rules replace
298
+ with flat keys such as `model` and `prompt`. See
299
+ [policy configuration](https://c15t.com/docs/self-host/guides/policy-packs).
300
+
301
+ The v3 backend stores timestamps in UTC. Convert rows that a v2 backend wrote
302
+ in another time zone before you deploy. Database setup lists the checks.
303
+
304
+ Other backend options and entries change too:
305
+
306
+ | v2 | v3 |
307
+ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
308
+ | `tablePrefix` | Removed. The migrator refuses a database with prefixed c15t tables, so rename them first. On PostgreSQL, `database.schema` keeps c15t's tables in their own schema. |
309
+ | `iab.vendorIds`, `iab.endpoint`, `cache.adapter` | `gvl: { vendorIds, endpoint, cache }` |
310
+ | `iab.enabled`, `iab.cmpId`, `iab.customVendors` | `manifest.iab` |
311
+ | `logger`, `telemetry` | `observability`, passed to `c15tInstance()` |
312
+ | `background`, `iab.bundled` | Removed |
313
+ | `openapi.docsPath`, `openapi.options`, `openapi.customUiTemplate` and the `/docs` page | Removed. The spec is still at `/spec.json`. |
314
+ | `policySnapshot.onValidationFailure` | Removed |
315
+ | `GET /` as an alias of `/status` | Removed. Call `/status`. |
316
+ | `@c15t/backend/define-config`, `/router`, `/types`, `/db/schema`, `/db/adapters/*`, `/db/migrator`, `/edge` | Removed. Import `defineConfig`, `createMigrator` and `policyRulePresets` from `@c15t/backend`. |
317
+
440
318
  ### Remove `disableGeoLocation`
441
319
 
442
- v3 removes the `disableGeoLocation` manifest option. Policy rules decide by
320
+ v3 removes the top-level `disableGeoLocation` option. Policy rules decide by
443
321
  country and region. To show every visitor the same banner, configure one rule
444
322
  with `match: { isDefault: true }`. It applies wherever the visitor is, so the
445
323
  browser can resolve it without asking the backend for a location.
@@ -488,51 +366,64 @@ if (result.ok) {
488
366
  }
489
367
  ```
490
368
 
491
- | v2 | v3 |
492
- | ------------------------------------------------------------------- | -------------------------------------------------------------------------- |
493
- | `c15tClient(options)`, `new C15TClient(options)` | `createC15tClient(options)` |
494
- | `C15T_API_URL`, `C15T_API_TOKEN` read from the environment | Not read. Pass `baseUrl` and `apiKey`. |
495
- | `token` | `apiKey` |
496
- | `prefix` | Part of `baseUrl`, such as `https://app.example.com/api/c15t` |
497
- | `timeout` | `timeoutMs`, per attempt |
498
- | `retryConfig` | `retry: { maxRetries, initialDelayMs, maxDelayMs }`, or `false` |
499
- | `debug`, `C15T_DEBUG` | `onEvent` |
500
- | `client.meta.status()`, `client.status()` | `c15t.status()` |
501
- | `client.meta.init()`, `client.init()` | `c15t.init({ language, country, region, gpc })` |
502
- | `getSubject(id, { type: 'a,b' })` | `subjects.get(id, { types: ['a', 'b'] })` |
503
- | `createSubject(input)` | `subjects.create(input)`. `givenAt` also takes a `Date`. |
504
- | `patchSubject(id, input)`, `subjects.patch(id, input)` | `subjects.identify(id, { externalId, identityProvider })` |
505
- | `listSubjects({ externalId })` | `subjects.list({ externalId })`, on a client with `apiKey` |
506
- | `checkConsent(query)`, `consent.check({ externalId, type: 'a,b' })` | `consents.check({ externalId, types: ['a', 'b'] })` |
507
- | `summarizeExperiment(id, query)`, `experiments.summary(id, query)` | `experiments.summary(id, { from, to, domain })`, on a client with `apiKey` |
508
- | `ResponseContext` with `data: T \|null` | `{ ok: true, data } \|{ ok: false, error }`. Check `ok` first. |
509
- | `unwrap()`, `expect()` on the response | `unwrap(result)`, imported from `@c15t/node-sdk` |
510
- | `unwrapOr()`, `map()` on the response | Check `result.ok` |
511
- | `throw: true` | `unwrap(result)` |
512
- | `onSuccess`, `onError` | Check `result.ok` after the call |
513
- | `C15TError`, `isC15TError()` | `C15tError`, `isC15tError(error, ...codes)` |
514
- | `$fetch`, `fetcher`, `resolveUrl`, `createResponseContext` | Removed. Pass a custom `fetch` to `createC15tClient` if you need one. |
515
- | `createMockClient`, `createMockResponse`, `createMockErrorResponse` | `createMockC15tClient`, `ok`, `err` from `@c15t/node-sdk/testing` |
369
+ | v2 | v3 |
370
+ | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
371
+ | `c15tClient(options)`, `new C15TClient(options)` | `createC15tClient(options)` |
372
+ | `C15T_API_URL`, `C15T_API_TOKEN` read from the environment | Not read. Pass `baseUrl` and `apiKey`. |
373
+ | `token` | `apiKey` |
374
+ | `prefix` | Part of `baseUrl`. v2 replaced the base URL's path with `prefix`, so use the origin plus the prefix, such as `https://app.example.com/api/c15t`. |
375
+ | `timeout` | `timeoutMs`, per attempt. The default drops from 30 to 10 seconds. |
376
+ | `retryConfig` | `retry: { maxRetries, initialDelayMs, maxDelayMs }`, or `false`. `backoffFactor`, `retryableStatusCodes`, `nonRetryableStatusCodes` and `retryOnNetworkError` are gone. |
377
+ | `debug`, `C15T_DEBUG` | `onEvent` |
378
+ | `client.meta.status()`, `client.status()` | `c15t.status()` |
379
+ | `client.meta.init(options)`, `client.init(options)` | `c15t.init({ language, country, region, gpc }, options)`. Per-call options move to the second argument. |
380
+ | `getSubject(id, { type: 'a,b' })`, `subjects.get(id, { type: 'a,b' })` | `subjects.get(id, { types: ['a', 'b'] })` |
381
+ | `createSubject(input)` | `subjects.create(input)`. `givenAt` also takes a `Date`. |
382
+ | `patchSubject(id, input)`, `subjects.patch(id, input)` | `subjects.identify(id, { externalId, identityProvider })` |
383
+ | `listSubjects({ externalId })`, `subjects.list({ externalId })` | `subjects.list({ externalId })`, on a client with `apiKey` |
384
+ | `checkConsent(query)`, `consent.check({ externalId, type: 'a,b' })` | `consents.check({ externalId, types: ['a', 'b'] })`, on a client with `apiKey` |
385
+ | `ResponseContext` with `data: T \|null` | `{ ok: true, data } \|{ ok: false, error }`. Check `ok` first. |
386
+ | `unwrap()`, `expect()` on the response | `unwrap(result)`, imported from `@c15t/node-sdk` |
387
+ | `unwrapOr()`, `map()` on the response | Check `result.ok` |
388
+ | `throw: true` | `unwrap(result)` |
389
+ | `onSuccess`, `onError` | Check `result.ok` after the call |
390
+ | `C15TError`, `isC15TError()` | `C15tError`, `isC15tError(error, ...codes)` |
391
+ | `C15TClient`, `C15TClientOptions`, `RetryConfig`, `FetchOptions`, `ResponseContext` types | `C15tClient`, `C15tClientOptions`, `C15tRetryOptions`, `C15tCallOptions`, `C15tResult` |
392
+ | Per-call `timeout`, `retryConfig` | `timeoutMs`, `retry` |
393
+ | Per-call `body`, `query`, `method` | Removed. Pass the body and query as the method's arguments. |
394
+ | `$fetch`, `fetcher`, `resolveUrl`, `createResponseContext` | Removed. Pass a custom `fetch` to `createC15tClient` if you need one. |
395
+ | `createMockClient`, `createMockResponse`, `createMockErrorResponse` | `createMockC15tClient`, `ok`, `err` from `@c15t/node-sdk/testing` |
516
396
 
517
397
  `patchSubject` returned `{ success, subject }`. `subjects.identify` returns
518
398
  `{ subject }`, and `subject.identityProvider` is always set. Dates in
519
399
  responses, such as `givenAt` and `createdAt`, are now `Date` objects rather
520
400
  than strings.
521
401
 
522
- The new client also adds `manifest()` and `legalDocuments.publish()`. See the
523
- [Node.js SDK reference](https://c15t.com/docs/self-host/api/node-sdk).
402
+ `consents.check` and `subjects.list` count only verified links. Links made by
403
+ v2 don't count until you relink them with `subjects.identify` on a keyed
404
+ client, or [with an identity token](https://c15t.com/docs/self-host/api/node-sdk#verify-a-link-made-in-the-browser) on the user's next sign-in.
405
+
406
+ The `node-sdk-to-v3` codemod renames the factory, client options, methods,
407
+ types and errors in files that create a client. It turns `type` strings into
408
+ `types` arrays, and leaves a `TODO(c15t v3)` comment at each call, because
409
+ results are now `{ ok, data }` or `{ ok, error }`. It renames per-call
410
+ `timeout` and `retryConfig` too. It also marks `prefix`, `debug`, per-call
411
+ `throw`, `onSuccess`, `onError`, `body`, `query` and `method`, and a client
412
+ built from environment variables:
413
+
414
+ ```bash
415
+ npx @c15t/cli@alpha codemods node-sdk-to-v3 --dry-run --json
416
+ ```
524
417
 
525
- ## Check the migration
418
+ The new client also adds `manifest()`, `legalDocuments.publish()` and
419
+ `experiments.summary()`. The last two need a client with `apiKey`. See the
420
+ [Node.js SDK reference](https://c15t.com/docs/self-host/api/node-sdk).
526
421
 
527
- 1. Run your typecheck and build. Remaining `TODO(c15t v3)` comments from the
528
- codemod fail the build until you resolve them.
529
- 2. Load the app with a v2 consent cookie from before the upgrade. The banner
530
- stays closed if that choice still applies, and a rejected category stays
531
- blocked.
532
- 3. In a private window, check in DevTools Network that vendor requests wait for
533
- consent, start after you accept, and stay absent after you reject and
534
- reload.
535
- 4. Reopen preferences from your privacy settings link and change a category.
422
+ ## Update the CLI
536
423
 
537
- The [verification guide](./guides/verify-consent.md) has the full release
538
- checklist.
424
+ * Run `c15t login` again. The CLI signs in through Inth now and no longer
425
+ reads sessions saved by v2.
426
+ * `c15t setup` no longer writes `.env.local` or `.env.example` and no longer
427
+ accepts `--env`. It writes the backend URL into the files it generates.
428
+ * `c15t setup` no longer detects Remix or Gatsby. Pick a guide from
429
+ [Choose your setup](./concepts/choose-your-setup.md) for those apps.