@c15t/scripts 3.0.0-alpha.2 → 3.0.0-alpha.4

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 (300) hide show
  1. package/AGENTS.md +129 -63
  2. package/README.md +8 -29
  3. package/SKILL.md +33 -0
  4. package/dist/adobe-analytics.js +2 -0
  5. package/dist/ahrefs-analytics.js +2 -0
  6. package/dist/amplitude.js +2 -0
  7. package/dist/clearbit.js +2 -0
  8. package/dist/cloudflare-web-analytics.js +2 -0
  9. package/dist/cloudflare-zaraz.js +2 -0
  10. package/dist/crisp.js +2 -0
  11. package/dist/databuddy.js +2 -0
  12. package/dist/e2e-test-utils.js +2 -139
  13. package/dist/engine/compile.js +2 -89
  14. package/dist/engine/runtime.js +2 -448
  15. package/dist/events.js +2 -218
  16. package/dist/fathom-analytics.js +2 -0
  17. package/dist/front-chat.js +2 -0
  18. package/dist/google-tag-manager.js +2 -0
  19. package/dist/google-tag.js +2 -0
  20. package/dist/heap.js +2 -0
  21. package/dist/hightouch.js +2 -0
  22. package/dist/hotjar.js +2 -0
  23. package/dist/intercom.js +2 -0
  24. package/dist/klaviyo.js +2 -0
  25. package/dist/linkedin-insights.js +2 -0
  26. package/dist/logrocket.js +2 -0
  27. package/dist/matomo-analytics.js +2 -0
  28. package/dist/meta-pixel.js +2 -0
  29. package/dist/microsoft-clarity.js +2 -0
  30. package/dist/microsoft-uet.js +2 -0
  31. package/dist/mixpanel-analytics.js +2 -0
  32. package/dist/one-dollar-stats.js +2 -0
  33. package/dist/openai-pixel.js +2 -0
  34. package/dist/pinterest-tag.js +2 -0
  35. package/dist/pirsch.js +2 -0
  36. package/dist/plausible-analytics.js +2 -0
  37. package/dist/posthog.js +2 -0
  38. package/dist/promptwatch.js +2 -0
  39. package/dist/reddit-pixel.js +2 -0
  40. package/dist/registry.js +2 -422
  41. package/dist/resolve.js +2 -33
  42. package/dist/rudderstack.js +2 -0
  43. package/dist/rybbit-analytics.js +2 -0
  44. package/dist/segment.js +2 -0
  45. package/dist/snapchat-pixel.js +2 -0
  46. package/dist/tiktok-pixel.js +2 -0
  47. package/dist/types.js +2 -16
  48. package/dist/umami-analytics.js +2 -0
  49. package/dist/vendors/_shared/attributes.js +2 -14
  50. package/dist/vendors/_shared/google-consent.js +2 -27
  51. package/dist/vendors/_shared/install-builders.js +2 -21
  52. package/dist/vendors/_shared/required-id.js +2 -0
  53. package/dist/vendors/_shared/script-url.js +2 -28
  54. package/dist/vendors/ads-and-pixels/linkedin-insights.js +2 -48
  55. package/dist/vendors/ads-and-pixels/meta-pixel.js +2 -153
  56. package/dist/vendors/ads-and-pixels/microsoft-uet.js +2 -110
  57. package/dist/vendors/ads-and-pixels/openai-pixel.js +2 -88
  58. package/dist/vendors/ads-and-pixels/pinterest-tag.js +2 -123
  59. package/dist/vendors/ads-and-pixels/reddit-pixel.js +2 -107
  60. package/dist/vendors/ads-and-pixels/snapchat-pixel.js +2 -87
  61. package/dist/vendors/ads-and-pixels/tiktok-pixel.js +2 -89
  62. package/dist/vendors/ads-and-pixels/x-pixel.js +2 -48
  63. package/dist/vendors/analytics/adobe-analytics.js +2 -49
  64. package/dist/vendors/analytics/ahrefs-analytics.js +2 -27
  65. package/dist/vendors/analytics/amplitude.js +2 -134
  66. package/dist/vendors/analytics/clearbit.js +2 -28
  67. package/dist/vendors/analytics/cloudflare-web-analytics.js +2 -32
  68. package/dist/vendors/analytics/databuddy.js +2 -103
  69. package/dist/vendors/analytics/fathom-analytics.js +2 -35
  70. package/dist/vendors/analytics/google-tag.js +2 -78
  71. package/dist/vendors/analytics/heap.js +2 -134
  72. package/dist/vendors/analytics/hightouch.js +2 -109
  73. package/dist/vendors/analytics/hotjar.js +2 -44
  74. package/dist/vendors/analytics/logrocket.js +2 -58
  75. package/dist/vendors/analytics/matomo-analytics.js +2 -191
  76. package/dist/vendors/analytics/microsoft-clarity.js +2 -100
  77. package/dist/vendors/analytics/mixpanel-analytics.js +2 -93
  78. package/dist/vendors/analytics/one-dollar-stats.js +2 -30
  79. package/dist/vendors/analytics/pirsch.js +2 -67
  80. package/dist/vendors/analytics/plausible-analytics.js +2 -81
  81. package/dist/vendors/analytics/posthog.js +2 -200
  82. package/dist/vendors/analytics/promptwatch.js +2 -29
  83. package/dist/vendors/analytics/rudderstack.js +2 -183
  84. package/dist/vendors/analytics/rybbit-analytics.js +2 -63
  85. package/dist/vendors/analytics/segment.js +2 -65
  86. package/dist/vendors/analytics/umami-analytics.js +2 -39
  87. package/dist/vendors/analytics/vercel-analytics.js +2 -53
  88. package/dist/vendors/email-and-sms/klaviyo.js +2 -0
  89. package/dist/vendors/functional/crisp.js +2 -100
  90. package/dist/vendors/functional/front-chat.js +2 -64
  91. package/dist/vendors/functional/intercom.js +2 -45
  92. package/dist/vendors/tag-managers/cloudflare-zaraz.js +2 -98
  93. package/dist/vendors/tag-managers/google-tag-manager.js +2 -73
  94. package/dist/vercel-analytics.js +2 -0
  95. package/dist/x-pixel.js +2 -0
  96. package/dist-types/adobe-analytics.d.ts +2 -0
  97. package/dist-types/ahrefs-analytics.d.ts +2 -0
  98. package/dist-types/amplitude.d.ts +2 -0
  99. package/dist-types/clearbit.d.ts +2 -0
  100. package/dist-types/cloudflare-web-analytics.d.ts +2 -0
  101. package/dist-types/cloudflare-zaraz.d.ts +2 -0
  102. package/dist-types/crisp.d.ts +2 -0
  103. package/dist-types/databuddy.d.ts +2 -0
  104. package/dist-types/e2e-test-utils.d.ts +2 -0
  105. package/dist-types/engine/compile.d.ts +2 -3
  106. package/dist-types/engine/runtime.d.ts +2 -3
  107. package/dist-types/events.d.ts +2 -46
  108. package/dist-types/fathom-analytics.d.ts +2 -0
  109. package/dist-types/front-chat.d.ts +2 -0
  110. package/dist-types/google-tag-manager.d.ts +2 -0
  111. package/dist-types/google-tag.d.ts +2 -0
  112. package/dist-types/heap.d.ts +2 -0
  113. package/dist-types/hightouch.d.ts +2 -0
  114. package/dist-types/hotjar.d.ts +2 -0
  115. package/dist-types/intercom.d.ts +2 -0
  116. package/dist-types/klaviyo.d.ts +2 -0
  117. package/dist-types/linkedin-insights.d.ts +2 -0
  118. package/dist-types/logrocket.d.ts +2 -0
  119. package/dist-types/matomo-analytics.d.ts +2 -0
  120. package/dist-types/meta-pixel.d.ts +2 -0
  121. package/dist-types/microsoft-clarity.d.ts +2 -0
  122. package/dist-types/microsoft-uet.d.ts +2 -0
  123. package/dist-types/mixpanel-analytics.d.ts +2 -0
  124. package/dist-types/one-dollar-stats.d.ts +2 -0
  125. package/dist-types/openai-pixel.d.ts +2 -0
  126. package/dist-types/pinterest-tag.d.ts +2 -0
  127. package/dist-types/pirsch.d.ts +2 -0
  128. package/dist-types/plausible-analytics.d.ts +2 -0
  129. package/dist-types/posthog.d.ts +2 -0
  130. package/dist-types/promptwatch.d.ts +2 -0
  131. package/dist-types/reddit-pixel.d.ts +2 -0
  132. package/dist-types/registry.d.ts +2 -485
  133. package/dist-types/resolve.d.ts +2 -9
  134. package/dist-types/rudderstack.d.ts +2 -0
  135. package/dist-types/rybbit-analytics.d.ts +2 -0
  136. package/dist-types/segment.d.ts +2 -0
  137. package/dist-types/snapchat-pixel.d.ts +2 -0
  138. package/dist-types/tiktok-pixel.d.ts +2 -0
  139. package/dist-types/types.d.ts +2 -314
  140. package/dist-types/umami-analytics.d.ts +2 -0
  141. package/dist-types/vendors/_shared/attributes.d.ts +2 -35
  142. package/dist-types/vendors/_shared/google-consent.d.ts +2 -47
  143. package/dist-types/vendors/_shared/install-builders.d.ts +2 -30
  144. package/dist-types/vendors/_shared/required-id.d.ts +2 -0
  145. package/dist-types/vendors/_shared/script-url.d.ts +2 -75
  146. package/dist-types/vendors/ads-and-pixels/linkedin-insights.d.ts +2 -92
  147. package/dist-types/vendors/ads-and-pixels/meta-pixel.d.ts +2 -289
  148. package/dist-types/vendors/ads-and-pixels/microsoft-uet.d.ts +2 -105
  149. package/dist-types/vendors/ads-and-pixels/openai-pixel.d.ts +2 -211
  150. package/dist-types/vendors/ads-and-pixels/pinterest-tag.d.ts +2 -295
  151. package/dist-types/vendors/ads-and-pixels/reddit-pixel.d.ts +2 -210
  152. package/dist-types/vendors/ads-and-pixels/snapchat-pixel.d.ts +2 -171
  153. package/dist-types/vendors/ads-and-pixels/tiktok-pixel.d.ts +2 -106
  154. package/dist-types/vendors/ads-and-pixels/x-pixel.d.ts +2 -183
  155. package/dist-types/vendors/analytics/adobe-analytics.d.ts +2 -75
  156. package/dist-types/vendors/analytics/ahrefs-analytics.d.ts +2 -62
  157. package/dist-types/vendors/analytics/amplitude.d.ts +2 -234
  158. package/dist-types/vendors/analytics/clearbit.d.ts +2 -60
  159. package/dist-types/vendors/analytics/cloudflare-web-analytics.d.ts +2 -67
  160. package/dist-types/vendors/analytics/databuddy.d.ts +2 -147
  161. package/dist-types/vendors/analytics/fathom-analytics.d.ts +2 -90
  162. package/dist-types/vendors/analytics/google-tag.d.ts +2 -95
  163. package/dist-types/vendors/analytics/heap.d.ts +2 -316
  164. package/dist-types/vendors/analytics/hightouch.d.ts +2 -285
  165. package/dist-types/vendors/analytics/hotjar.d.ts +2 -73
  166. package/dist-types/vendors/analytics/logrocket.d.ts +2 -101
  167. package/dist-types/vendors/analytics/matomo-analytics.d.ts +2 -41
  168. package/dist-types/vendors/analytics/microsoft-clarity.d.ts +2 -97
  169. package/dist-types/vendors/analytics/mixpanel-analytics.d.ts +2 -113
  170. package/dist-types/vendors/analytics/one-dollar-stats.d.ts +2 -39
  171. package/dist-types/vendors/analytics/pirsch.d.ts +2 -96
  172. package/dist-types/vendors/analytics/plausible-analytics.d.ts +2 -122
  173. package/dist-types/vendors/analytics/posthog.d.ts +2 -175
  174. package/dist-types/vendors/analytics/promptwatch.d.ts +2 -36
  175. package/dist-types/vendors/analytics/rudderstack.d.ts +2 -330
  176. package/dist-types/vendors/analytics/rybbit-analytics.d.ts +2 -82
  177. package/dist-types/vendors/analytics/segment.d.ts +2 -164
  178. package/dist-types/vendors/analytics/umami-analytics.d.ts +2 -93
  179. package/dist-types/vendors/analytics/vercel-analytics.d.ts +2 -66
  180. package/dist-types/vendors/email-and-sms/klaviyo.d.ts +2 -0
  181. package/dist-types/vendors/functional/crisp.d.ts +2 -78
  182. package/dist-types/vendors/functional/front-chat.d.ts +2 -62
  183. package/dist-types/vendors/functional/intercom.d.ts +2 -135
  184. package/dist-types/vendors/tag-managers/cloudflare-zaraz.d.ts +2 -39
  185. package/dist-types/vendors/tag-managers/google-tag-manager.d.ts +2 -96
  186. package/dist-types/vercel-analytics.d.ts +2 -0
  187. package/dist-types/x-pixel.d.ts +2 -0
  188. package/docs/README.md +129 -63
  189. package/docs/assets/v3/bottom-bar.png +0 -0
  190. package/docs/assets/v3/brand-card.png +0 -0
  191. package/docs/assets/v3/brand-preferences.png +0 -0
  192. package/docs/assets/v3/choice-wall.png +0 -0
  193. package/docs/assets/v3/headless-bar-html.png +0 -0
  194. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  195. package/docs/assets/v3/headless-bar.png +0 -0
  196. package/docs/assets/v3/slim-bar.png +0 -0
  197. package/docs/concepts/choose-your-setup.md +87 -0
  198. package/docs/concepts/consent-categories.md +84 -0
  199. package/docs/{guides → concepts}/consent-state.md +89 -105
  200. package/docs/{guides → concepts}/data-fetching.md +31 -27
  201. package/docs/concepts/how-consent-works.md +123 -0
  202. package/docs/concepts/policies.md +71 -0
  203. package/docs/customization/class-names.md +202 -0
  204. package/docs/customization/dark-mode.md +157 -0
  205. package/docs/customization/motion.md +119 -0
  206. package/docs/customization/overview.md +67 -34
  207. package/docs/customization/recipes.md +839 -49
  208. package/docs/customization/slots.md +216 -35
  209. package/docs/customization/stylesheets.md +147 -0
  210. package/docs/customization/tailwind.md +842 -0
  211. package/docs/customization/tokens.md +163 -96
  212. package/docs/customization/translations.md +60 -3
  213. package/docs/frameworks/astro/embeds.md +160 -0
  214. package/docs/frameworks/astro/network-blocker.md +86 -0
  215. package/docs/frameworks/astro/scripts.md +155 -0
  216. package/docs/frameworks/html/embeds.md +142 -0
  217. package/docs/frameworks/html/network-blocker.md +105 -0
  218. package/docs/frameworks/html/scripts.md +164 -0
  219. package/docs/frameworks/javascript/scripts.md +137 -0
  220. package/docs/frameworks/next/embeds.md +90 -0
  221. package/docs/frameworks/next/network-blocker.md +153 -0
  222. package/docs/frameworks/next/scripts.md +196 -0
  223. package/docs/frameworks/nuxt/embeds.md +81 -0
  224. package/docs/frameworks/nuxt/network-blocker.md +97 -0
  225. package/docs/frameworks/nuxt/scripts.md +89 -0
  226. package/docs/frameworks/react/embeds.md +89 -0
  227. package/docs/frameworks/react/network-blocker.md +140 -0
  228. package/docs/frameworks/react/scripts.md +115 -0
  229. package/docs/frameworks/svelte/embeds.md +96 -0
  230. package/docs/frameworks/svelte/network-blocker.md +141 -0
  231. package/docs/frameworks/svelte/scripts.md +144 -0
  232. package/docs/frameworks/sveltekit/embeds.md +103 -0
  233. package/docs/frameworks/sveltekit/network-blocker.md +159 -0
  234. package/docs/frameworks/sveltekit/scripts.md +172 -0
  235. package/docs/frameworks/tanstack-start/embeds.md +96 -0
  236. package/docs/frameworks/tanstack-start/network-blocker.md +145 -0
  237. package/docs/frameworks/tanstack-start/scripts.md +103 -0
  238. package/docs/frameworks/vue/embeds.md +84 -0
  239. package/docs/frameworks/vue/network-blocker.md +99 -0
  240. package/docs/frameworks/vue/scripts.md +93 -0
  241. package/docs/guides/banner-experiments.md +654 -0
  242. package/docs/guides/troubleshooting.md +120 -47
  243. package/docs/guides/verify-consent.md +81 -49
  244. package/docs/integrations/adobe-analytics.md +167 -159
  245. package/docs/integrations/ahrefs-analytics.md +152 -154
  246. package/docs/integrations/amplitude.md +162 -156
  247. package/docs/integrations/building-integrations.md +136 -37
  248. package/docs/integrations/clearbit.md +154 -154
  249. package/docs/integrations/cloudflare-web-analytics.md +156 -156
  250. package/docs/integrations/cloudflare-zaraz.md +209 -261
  251. package/docs/integrations/crisp.md +164 -158
  252. package/docs/integrations/databuddy.md +157 -173
  253. package/docs/integrations/fathom-analytics.md +158 -156
  254. package/docs/integrations/front-chat.md +167 -167
  255. package/docs/integrations/google-maps.md +118 -83
  256. package/docs/integrations/google-tag-manager.md +178 -163
  257. package/docs/integrations/google-tag.md +163 -160
  258. package/docs/integrations/heap.md +163 -155
  259. package/docs/integrations/hightouch.md +161 -157
  260. package/docs/integrations/hotjar.md +159 -155
  261. package/docs/integrations/intercom.md +183 -153
  262. package/docs/integrations/klaviyo.md +486 -0
  263. package/docs/integrations/linkedin-insights.md +174 -150
  264. package/docs/integrations/logrocket.md +160 -156
  265. package/docs/integrations/matomo-analytics.md +188 -178
  266. package/docs/integrations/meta-pixel.md +188 -150
  267. package/docs/integrations/microsoft-clarity.md +163 -155
  268. package/docs/integrations/microsoft-uet.md +148 -154
  269. package/docs/integrations/mixpanel-analytics.md +155 -160
  270. package/docs/integrations/one-dollar-stats.md +166 -165
  271. package/docs/integrations/openai-pixel.md +204 -301
  272. package/docs/integrations/overview.md +137 -80
  273. package/docs/integrations/pinterest-tag.md +191 -183
  274. package/docs/integrations/pirsch.md +169 -159
  275. package/docs/integrations/plausible-analytics.md +172 -158
  276. package/docs/integrations/posthog.md +313 -240
  277. package/docs/integrations/promptwatch.md +154 -154
  278. package/docs/integrations/reddit-pixel.md +185 -157
  279. package/docs/integrations/rudderstack.md +201 -186
  280. package/docs/integrations/rybbit-analytics.md +171 -160
  281. package/docs/integrations/segment.md +182 -154
  282. package/docs/integrations/snapchat-pixel.md +186 -156
  283. package/docs/integrations/tiktok-pixel.md +171 -150
  284. package/docs/integrations/umami-analytics.md +163 -158
  285. package/docs/integrations/vercel-analytics.md +166 -157
  286. package/docs/integrations/x-pixel.md +176 -150
  287. package/docs/integrations/youtube.md +121 -86
  288. package/docs/upgrade-v3.md +496 -467
  289. package/package.json +10 -257
  290. package/dist-types/__tests__/helpers.d.ts +0 -141
  291. package/docs/assets/v3/brand-bar.png +0 -0
  292. package/docs/assets/v3/mobile-card.png +0 -0
  293. package/docs/assets/v3/preferences.png +0 -0
  294. package/docs/frameworks/javascript/script-loader.md +0 -100
  295. package/docs/frameworks/next/script-loader.md +0 -216
  296. package/docs/frameworks/react/script-loader.md +0 -69
  297. package/docs/guides/deployment-modes.md +0 -75
  298. package/docs/guides/shared-consent-controls.md +0 -158
  299. package/docs/integrations/clear-on-revocation.md +0 -167
  300. package/docs/integrations/granular-consent.md +0 -210
@@ -1,509 +1,538 @@
1
1
  ---
2
- title: Upgrade to v3 policies
3
- description: Migrate policy configuration, consent records, callbacks, and
4
- custom transports to the v3 policy system.
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.
5
6
  group: reference
6
7
  ---
7
8
 
8
- ## React version requirement
9
+ ## What changes in v3
10
+
11
+ * One package, `c15t`, replaces `@c15t/nextjs`, `@c15t/react` and the v2
12
+ `c15t` store. Import from its subpaths: `c15t/next`, `c15t/react`, and `c15t`
13
+ for the headless engine.
14
+ * The vendor helpers move from `@c15t/scripts` to `@c15t/integrations`, with
15
+ the same subpaths and helper names.
16
+ * `ConsentManagerProvider` becomes `ConsentProvider` in React. Next.js apps
17
+ that resolve consent on the server use `ConsentRoot`.
18
+ * `mode` and `backendURL` become one transport, `hosted({ url })` or
19
+ `offline()`.
20
+ * Policy packs become policy rules. You set them in your Inth project, in a
21
+ self-hosted backend's `manifest.policyRules`, or in `offline({ policyRules })`.
22
+ * `useConsentManager()` is gone. Each field has its own hook, and a codemod
23
+ rewrites most calls.
24
+ * Callbacks have new names, and each reports either a recorded choice or a
25
+ permission change.
26
+ * Visitors' existing choices keep working. v3 reads the v2 cookie.
27
+ * A self-hosted backend takes `database` instead of `adapter`, and needs a
28
+ schema migration.
29
+ * `@c15t/node-sdk` has a new client, `createC15tClient()`. Its methods return
30
+ results instead of throwing, and it reads no environment variables.
31
+
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.
77
+
78
+ ## Rename the integrations dependency
79
+
80
+ v3 renames `@c15t/scripts` to `@c15t/integrations`. Remove `@c15t/scripts`
81
+ from your dependencies and install `@c15t/integrations` from the `alpha`
82
+ dist-tag:
83
+
84
+ | Package manager | Command |
85
+ | :-------------- | :------------------------------------- |
86
+ | npm | `npm install @c15t/integrations@alpha` |
87
+ | pnpm | `pnpm add @c15t/integrations@alpha` |
88
+ | yarn | `yarn add @c15t/integrations@alpha` |
89
+ | bun | `bun add @c15t/integrations@alpha` |
90
+
91
+ Replace the package name in each import. Vendor subpaths and helper names stay
92
+ the same.
9
93
 
10
- The v3 React and Next.js adapters require React and React DOM 18 or newer.
11
- Upgrade both packages together before installing v3. Earlier alpha peer ranges
12
- incorrectly allowed React 16 and 17 even though v3 already uses `useId` and
13
- `useSyncExternalStore`, which those versions do not provide.
14
-
15
- ## Start with a policy rule
94
+ Before, in v2 and earlier v3 alphas:
16
95
 
17
96
  ```ts
18
- import { policyRulePresets } from '@c15t/schema';
19
- import { offline } from '@c15t/react';
20
-
21
- const mode = offline({
22
- policyRules: [policyRulePresets.europeOptIn()],
23
- });
97
+ import { createEventDispatcher } from '@c15t/scripts/events';
98
+ import { posthog } from '@c15t/scripts/posthog';
24
99
  ```
25
100
 
26
- Pass `mode` to `ConsentProvider`. For a backend integration, configure
27
- `policyRules` on the backend and use `hosted({ url })` in the provider. Follow the
28
- [React quickstart](https://c15t.com/docs/frameworks/react/quickstart),
29
- [Next.js quickstart](https://c15t.com/docs/frameworks/next/quickstart), or
30
- [JavaScript quickstart](https://c15t.com/docs/frameworks/javascript/quickstart) for a complete
31
- integration.
101
+ After:
32
102
 
33
- The CLI no longer offers `offline-add-policy-packs`, which generated v2
34
- configuration. For offline integrations, configure `offline({ policyRules })`
35
- as shown above.
103
+ ```ts
104
+ import { createEventDispatcher } from '@c15t/integrations/events';
105
+ import { posthog } from '@c15t/integrations/posthog';
106
+ ```
36
107
 
37
- Replace legacy `policyPacks` and nested `consent` configuration with rules:
108
+ The `scripts` configuration option and the `Script` type keep their names.
109
+ `@c15t/scripts` stays available as a deprecated compatibility package for all
110
+ of v3. It re-exports the implementation and types of `@c15t/integrations`, so
111
+ you can migrate imports separately from the rest of the upgrade. Compatibility
112
+ ends in v4; versions already published stay on npm.
38
113
 
39
- ```ts
40
- import type { PolicyRule } from '@c15t/schema';
41
-
42
- const policyRules = [
43
- {
44
- id: 'default-opt-in',
45
- match: { fallback: true },
46
- model: 'opt-in',
47
- prompt: 'choice',
48
- categories: ['measurement', 'marketing'],
49
- scopeMode: 'strict',
50
- },
51
- ] satisfies PolicyRule[];
114
+ To preview the import changes in JavaScript and TypeScript files:
115
+
116
+ ```bash
117
+ npx @c15t/cli@alpha codemods scripts-to-integrations --dry-run --json
52
118
  ```
53
119
 
54
- `model` controls permission defaults. `prompt` controls whether the visitor must
55
- make a choice, dismiss a notice, or see no prompt. Opt-in and IAB models require
56
- `prompt: 'choice'`. Opt-out supports `choice`, `notice`, and `none`. The `none`
57
- model permits optional categories in scope, allows only `prompt: 'none'`, owes
58
- no rights, and renders no consent UI. v2's `none` model maps to it directly.
59
-
60
- When `categories` selects only some optional categories, you must set
61
- `scopeMode` explicitly. A strict scope blocks categories outside the rule. A permissive scope allows
62
- those categories unless another restriction applies. An omitted scope, `['*']`,
63
- or a list containing only `necessary` expands to the default optional categories.
64
- `necessary` is always permitted.
65
-
66
- `offline()` without `policyRules` resolves `recommendedPolicyRules()`, whose
67
- Europe rule also matches a visitor with no country. A known US country with a
68
- missing state uses US opt-out with GPC and persistent preferences; a missing
69
- Canadian province stays strict opt-in. The last rule is `none` for other known
70
- unmatched locations. Resolution exposes `matched`, `no-match`,
71
- `unconfigured`, or `failed`; while nothing has matched, optional categories stay
72
- denied and no consent surface renders. A missing or malformed policy never grants
73
- optional permissions by itself.
74
-
75
- ## Read the state you need
76
-
77
- | Purpose | Snapshot field | React hook |
78
- | ----------------------------------------------- | ---------------------- | --------------------------------------- |
79
- | Gate scripts and optional features | `effectivePermissions` | `useConsent(category)`, `useConsents()` |
80
- | Inspect recorded choices and confirmation times | `explicitChoice` | `useExplicitChoice()` |
81
- | Decide whether to prompt | `promptRequirement` | `usePromptRequirement()` |
82
- | Inspect the resolved rule | `policyRule` | `usePolicyRule()` |
83
- | Inspect matching or failure | `resolution` | `usePolicyResolution()` |
84
-
85
- An effective permission is not evidence of a grant. Under an opt-out rule it can
86
- be true before a visitor acts. A notice dismissal updates `noticeDismissal` and
87
- does not record consent. Global Privacy Control updates privacy signals and
88
- configured opt-out directives without turning a browser signal into a choice.
120
+ Review the output, then run it again without `--dry-run`. The codemod runs
121
+ only when you name it. It does not change `package.json`, lockfiles, or
122
+ imports inside `.vue`, `.svelte` or `.astro` files; update those by hand, then
123
+ reinstall dependencies and build the app.
89
124
 
90
125
  ## Replace `useConsentManager()`
91
126
 
92
- `useConsentManager()` is gone from `c15t/react`, `c15t/next`,
93
- `c15t/tanstack-start` and their `/headless` entries. It subscribed to the whole
94
- consent snapshot, so every component that called it re-rendered on every
95
- change, including changes to categories it never read. Call one hook per field
96
- instead; each re-renders only when its own value changes.
127
+ `useConsentManager()` returned the whole consent state, so every component
128
+ that called it re-rendered on every change. v3 has one hook per field. Run the
129
+ codemod first:
97
130
 
98
- Before, in v2 and earlier v3 alphas:
131
+ ```bash
132
+ npx @c15t/cli@alpha codemods use-consent-manager-to-hooks --dry-run --json
133
+ ```
134
+
135
+ Review the proposed files, then run it again without `--dry-run`. For this v2
136
+ component:
99
137
 
100
- ```tsx title="components/marketing-banner.tsx"
101
- import { useConsentManager } from 'c15t/react';
138
+ ```tsx title="Before (v2): components/accept-button.tsx"
139
+ import { useConsentManager } from '@c15t/react';
102
140
 
103
- export function MarketingBanner() {
104
- const { activeUI, has, saveConsents } = useConsentManager();
105
- if (activeUI !== 'banner' || has('marketing')) {
106
- return null;
107
- }
108
- return <button type="button" onClick={() => void saveConsents('all')}>Accept</button>;
141
+ export function AcceptButton() {
142
+ const { activeUI, has, saveConsents } = useConsentManager();
143
+ if (activeUI !== 'banner' || has('marketing')) return null;
144
+ return (
145
+ <button type="button" onClick={() => void saveConsents('all')}>
146
+ Accept
147
+ </button>
148
+ );
109
149
  }
110
150
  ```
111
151
 
112
- After:
113
-
114
- ```tsx title="components/marketing-banner.tsx"
115
- import { useActiveUI, useConsent } from 'c15t/react';
116
- import { useHeadlessConsentUI } from 'c15t/react/headless';
117
-
118
- export function MarketingBanner() {
119
- const activeUI = useActiveUI();
120
- const marketing = useConsent('marketing');
121
- const { performAction } = useHeadlessConsentUI();
122
- if (activeUI !== 'banner' || marketing) {
123
- return null;
124
- }
125
- return <button type="button" onClick={() => void performAction('accept')}>Accept</button>;
152
+ the codemod writes:
153
+
154
+ ```tsx title="After (v3): components/accept-button.tsx"
155
+ import { useActiveUI, useConsent } from '@c15t/react';
156
+ import { useHeadlessConsentUI } from '@c15t/react/headless';
157
+
158
+ export function AcceptButton() {
159
+ const activeUI = useActiveUI() ?? 'none';
160
+ const hasMarketing = useConsent('marketing');
161
+ const { saveCustomPreferences: saveConsents } = useHeadlessConsentUI();
162
+ if (activeUI !== 'banner' || hasMarketing) return null;
163
+ return (
164
+ <button type="button" onClick={() => void saveConsents('all')}>
165
+ Accept
166
+ </button>
167
+ );
126
168
  }
127
169
  ```
128
170
 
129
- Import hooks from the same entry you used for `useConsentManager()`:
130
- `c15t/next` and `c15t/next/headless` in Next.js, `c15t/tanstack-start` and
131
- `c15t/tanstack-start/headless` in TanStack Start.
132
-
133
- | `useConsentManager()` field | v3 replacement |
134
- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
135
- | `activeUI` | `useActiveUI()`. It returns `null` before the kernel picks a surface; the old field reported `'none'`. |
136
- | `setActiveUI(ui)` | `useSetActiveUI()` |
137
- | `has(category)` | `useConsent(category)`, one call per category at the top of the component |
138
- | `consents` | `useConsents()` |
139
- | `effectivePermissions` | `useEffectivePermissions()` |
140
- | `explicitChoice` | `useExplicitChoice()` |
141
- | `promptRequirement` | `usePromptRequirement()` |
142
- | `noticeDismissal` | `useNoticeDismissal()` |
143
- | `privacySignals` | `usePrivacySignals()` |
144
- | `optOutDirectives` | `useOptOutDirectives()` |
145
- | `restrictions` | `useRestrictions()` |
146
- | `resolution` | `usePolicyResolution()` |
147
- | `policyRule` | `usePolicyRule()` |
148
- | `policyCategories` | `usePolicyCategories()`. The old list started with `'necessary'`; this one does not. |
149
- | `policyScopeMode` | `usePolicyScopeMode()` |
150
- | `policyBanner` | `usePromptPresentation()` |
151
- | `policyDialog` | `usePreferencesPresentation()` |
152
- | `model` | `useModel()`. It returns `null` while no policy matches; the old field reported `'opt-in'`. |
153
- | `branding` | `useBranding()`. It returns `null` when unset; the old field reported `'c15t'`. |
154
- | `iab` | `useIABSnapshot()` |
155
- | `vendors` | `useDeclaredVendors()` |
156
- | `vendorChoice` | `useVendorChoice()` |
157
- | `getDisplayedVendors(category)` | `useDeclaredVendors()`, filtered to vendors whose category condition names the category |
158
- | `subscribeToConsentChanges(listener)` | `useSubscribeToConsentChanges()`, or the provider's `onPermissionsChanged` callback |
159
- | `updateConsentCategories(categories)` | `useRegisterConsentCategories()` |
160
- | `translationConfig` | `useTranslations()` for the active strings |
161
- | `selectedConsents`, `selectedConsentTypes` | `useConsentDraft().values` |
162
- | `setSelectedConsent(name, value)` | `useConsentDraft().set(name, value)` |
163
- | `selectedVendors` | `useConsentDraft().vendors`, or `useVendorDraft().vendors` |
164
- | `setSelectedVendor(id, granted)` | `useConsentDraft().setVendor(id, granted)` |
165
- | `resetDraft()` | `useConsentDraft().reset()` |
166
- | `draftIsStale` | `useConsentDraft().isStale` |
167
- | `consentCategories`, `consentTypes`, `getDisplayedConsents()` | `useConsentDraft().displayedCategories`, with labels from `useTranslations().consentTypes` |
168
- | `saveConsents('all')` | `useHeadlessConsentUI().performAction('accept')` |
169
- | `saveConsents('necessary')` | `useHeadlessConsentUI().performAction('reject')` |
170
- | `saveConsents('custom')` | `useHeadlessConsentUI().performAction('save')` |
171
- | `manager` | Nothing. It was always `null` in v3. |
172
-
173
- The old hook kept one private draft per component, shared by its selection and
174
- its save. `useConsentDraft()` and `useHeadlessConsentUI()` read the nearest
175
- `ConsentDraftProvider` instead. When one component stages choices and another
176
- saves them, or one component calls both hooks, render them inside the same
177
- `ConsentDraftProvider`; without one, each hook keeps its own draft and a custom
178
- save commits nothing you staged. `ConsentWidget` already provides one.
179
-
180
- `useHeadlessConsentUI()` saves the way the stock buttons do: it closes the
181
- surface after a successful save and refuses a draft that the policy made stale.
182
- `useSaveConsents()` calls the kernel directly and does neither.
183
-
184
- The CLI rewrites the common destructuring forms for you:
171
+ The codemod keeps the entry you imported from. Change `@c15t/react` to
172
+ `c15t/react` afterwards if you moved to the umbrella package. Fields it cannot
173
+ 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.
175
+ Import hooks from `c15t/react`, `c15t/next` or `c15t/tanstack-start`, and
176
+ `useHeadlessConsentUI` from the matching `/headless` entry.
177
+
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. |
208
+
209
+ `saveCustomPreferences()` saves the way the stock buttons do. It closes the
210
+ banner or dialog after a successful save. `useSaveConsents()` calls the engine
211
+ directly and does not close anything.
212
+
213
+ 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
218
+ adds a `TODO(c15t v3)` comment where this applies.
219
+
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';
185
226
 
186
- ```bash
187
- c15t codemods use-consent-manager-to-hooks --dry-run --json
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
+ }
188
244
  ```
189
245
 
190
- It replaces each field that has a one-hook equivalent, turns `has('marketing')`
191
- calls with a literal category into `useConsent('marketing')`, and maps
192
- `saveConsents('all' | 'necessary' | 'custom')` calls to
193
- `useHeadlessConsentUI().saveCustomPreferences()`. Fields it cannot rewrite stay
194
- on a `useConsentManager()` call under a `TODO(c15t v3)` comment that names the
195
- replacement, so the build fails at each place that needs manual work. It also
196
- marks components that now need a shared `ConsentDraftProvider`. Review the
197
- result, then run it without `--dry-run`.
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:
198
259
 
199
- ## Replace choice callbacks
260
+ ```tsx title="After (v3): components/consent-manager/provider.tsx, changed lines"
261
+ import { ConsentBanner, ConsentDialog, ConsentProvider, hosted } from 'c15t/next';
200
262
 
201
- ```tsx
263
+ // In the component, replace ConsentManagerProvider:
202
264
  <ConsentProvider
203
- options={{
204
- mode,
205
- callbacks: {
206
- onChoiceRecorded(event) {
207
- console.log('Visitor recorded a choice', event);
208
- },
209
- onPermissionsChanged(event) {
210
- console.log('Effective permissions changed', event);
211
- },
212
- },
213
- }}
265
+ options={{ mode: hosted({ url: 'https://your-project.c15t.dev' }) }}
214
266
  >
215
- {children}
216
- </ConsentProvider>
217
267
  ```
218
268
 
219
- Replace `onConsentSet` with `onChoiceRecorded` for explicit accept, reject, or
220
- save actions. Use `onPermissionsChanged` for permission changes caused by a
221
- choice, expiry, policy update, or privacy signal. Provider callbacks no longer
222
- include `onBannerFetched`; use the resolved policy and pending state to render
223
- loading UI.
224
-
225
- Only `kernel.commands.save()` creates an explicit choice. `save('all')` accepts,
226
- `save('none')` rejects, and `save({ marketing: false })` confirms only marketing.
227
- A partial save keeps the other categories' confirmation times. Use
228
- `commands.dismissNotice()` for the notice action and `kernel.hydrate()` to apply
229
- validated records without recording an action.
230
-
231
- ## Keep existing storage
232
-
233
- The reader accepts valid v2 storage and translates it into per-category receipts.
234
- It does not rewrite storage on startup. Existing denials continue to restrict
235
- permissions; expired or incompatible positive receipts cannot restore grants.
236
- The next explicit action writes the v3 record. Notice dismissal and privacy
237
- opt-out directives have separate records.
238
-
239
- Pass server-prepared `initialRecords`, `initialPolicyResolution`, and `now`
240
- through the adapter's prefetch configuration. Do not rebuild consent from a
241
- boolean `hasConsented` or copy effective permissions into explicit choice records.
242
- Pending prefetch results cannot restore records after a clear or overwrite a
243
- newer choice.
244
-
245
- ## Update custom transports and backend clients
246
-
247
- Use `KernelTransport` from `@c15t/core/transports`. Init returns a versioned
248
- `policyResolution` with a matched rule and fingerprints, or an explicit
249
- non-matched outcome. Use `mapInitOutputToInitResponse` for an HTTP init payload.
250
- Do not return legacy `policy` or `policyDecision` fields.
251
-
252
- Save requests contain the explicit choice and the categories confirmed by this
253
- action. Return a `SaveResult` with `ok`; optional identity and record methods
254
- must preserve the same receipt format. Forward the policy contract headers when
255
- implementing an HTTP proxy. Keep client, schema, backend, and framework adapters
256
- on compatible v3 versions.
257
-
258
- Use the supplied hosted or manifest transports to capture policy evidence for
259
- saves and retries. They preserve the action's policy, location, language, and
260
- privacy signal inputs. A stale policy assertion is an error; it must not silently
261
- save under a different rule. See the
262
- [backend endpoint reference](https://c15t.com/docs/self-host/api/endpoints) for wire fields.
263
-
264
- ## Shared runtime ownership
265
-
266
- Astro and SvelteKit can create a runtime outside a component tree with
267
- `createConsentRuntime` from `@c15t/core/runtime`. Pass that runtime to a
268
- framework provider with its `runtime` prop. The owner calls `start()` after
269
- mount and `dispose()` when the page no longer needs it. Borrowing providers
270
- subscribe to the kernel and render UI without initializing or disposing it.
271
-
272
- Runtime construction preserves the prepared server snapshot. Storage reads,
273
- privacy-signal activation and script loading begin on `start()`. Pass server
274
- records through `prefetch.initialRecords` with their evaluation time to keep
275
- the first browser render consistent with the server. `runtime.clearRecords()`
276
- clears both persisted and in-memory records.
277
-
278
- Runtime callbacks use `onChoiceRecorded`, `onPermissionsChanged` and
279
- `onError`. Hydrating records does not report a new visitor choice.
280
-
281
- Astro's serializable offline descriptor accepts `policyRules`, just like the
282
- other adapters' offline factories. Configure a preset or explicit rules;
283
- omitting them keeps the conservative fallback.
284
-
285
- ## Presentation corrections
286
-
287
- Use `blocking` for backdrop, scroll locking and focus trapping together.
288
- Choice banners default to non-blocking, choice walls always block, and notices
289
- never block. A notice configured as a wall falls back to a floating card.
290
- Preferences remain centered; `variant` and `position` apply only to prompts.
291
-
292
- Replace reads of `uncoveredRights` with `preferenceControls`. The latter is a
293
- list of additional preferences buttons recommended for the stock UI. It does
294
- not verify disclosure or persistent access to policy rights.
295
-
296
- Notice acknowledgement uses `common.acknowledge`, falling back to
297
- `common.dismiss` for older translation bundles. It does not record a choice.
298
-
299
- ## Astro notice acknowledgement
300
-
301
- ```astro
302
- ---
303
- import ConsentBanner from 'c15t/astro/components/consent-banner.astro';
304
- ---
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](https://c15t.com/docs/frameworks/react/quickstart) 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
305
350
 
306
- <ConsentBanner dismissButtonText="Got it" />
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
+ });
307
368
  ```
308
369
 
309
- Astro's `ConsentBanner` accepts `dismissButtonText` as an optional string. It
310
- labels the notice acknowledgement button, which defaults to
311
- `common.acknowledge` and falls back to `common.dismiss` in older translation
312
- bundles. Acknowledging a notice preserves the visitor's consent choices.
313
-
314
- ## IAB blocking behavior
315
-
316
- IAB banners and dialogs use the same `presentation.prompt.blocking` and
317
- `presentation.preferences.blocking` settings as the other consent components.
318
- A blocking surface shows a backdrop, traps focus and locks page scrolling.
319
- Explicit `blocking` values take precedence over deprecated `scrollLock` and
320
- `trapFocus` options. IAB components stay hidden without a matched policy,
321
- including when a dialog receives `open={true}`.
322
-
323
- The React consent dialog still blocks pointer interaction with the page when
324
- `blocking` is true and you hide its backdrop with `overlay={false}`.
325
-
326
- Legacy prompt options now control the whole blocking behavior. For example,
327
- `scrollLock: true` alone enables a backdrop and focus trapping as well as
328
- scroll locking. Set `blocking: false` explicitly for a non-modal banner.
329
-
330
- Custom dialog backdrops follow the same rule. A dialog resolved as non-blocking
331
- omits both the default backdrop and a supplied `overlay`. To retain a custom
332
- backdrop when migrating from `trapFocus: false`, set
333
- `presentation.preferences.blocking: true` and keep your `overlay` prop.
334
-
335
- ## Next.js and TanStack Start renames
336
-
337
- These names changed without deprecated aliases. `defineConsentConfig`,
338
- `ConsentConfig` (the URL config) and `ConsentProvider` from `@c15t/react` are
339
- unchanged.
340
-
341
- Next.js (`c15t/next`, `c15t/next/server`, `c15t/next/pages`):
342
-
343
- | v2 | v3 |
344
- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
345
- | `ConsentBoundary` | `ConsentRoot` |
346
- | `ConsentBoundaryProps` | `ConsentRootProps` |
347
- | `config={initialConsent}` (the visitor's resolved state) | `state={...}` |
348
- | `consent={consentConfig}` (the `defineConsentConfig` result) | `config={consentConfig}` |
349
- | `prefetchInitialConsent(options)` | `resolveConsent(options)` with `config` or `backendURL` |
350
- | `readInitialConsentConfig(options)` | `resolveConsent(options)` without `config` or `backendURL` |
351
- | `InitialConsentConfig`, and `KernelConfig` where it named the returned value | `ConsentState` |
352
- | `PrefetchInitialConsentOptions` | `ResolveConsentOptions` |
353
- | `ReadInitialConsentConfigOptions` | `ConsentRequestOptions` |
354
- | Pages Router `readInitialConsentConfig(req, opts)` and `prefetchInitialConsent({ req, ... })` | `resolveConsent({ req, ... })` |
355
-
356
- TanStack Start (`c15t/tanstack-start`, `c15t/tanstack-start/server`):
357
-
358
- | v2 | v3 |
359
- | ------------------------------------- | ---------------------------------------------- |
360
- | `ConsentBoundary` | `ConsentRoot` |
361
- | `config={config}` | `state={state}` |
362
- | `ConsentConfig` (the returned state) | `ConsentState` |
363
- | `prefetchInitialConsent` | `resolveConsent(options)` with `backendURL` |
364
- | `readInitialConsentConfig` | `resolveConsent(options)` without `backendURL` |
365
- | `createConsentConfigHandler(options)` | `createConsentStateHandler(options)` |
366
- | `mergeInitIntoConsentConfig` | `mergeInitIntoConsentState` |
367
-
368
- `consentLoaderOptions`, `backendURL`, `initRoute` and `DEFAULT_INIT_ROUTE` are
369
- unchanged.
370
-
371
- ## `Frame` is now `ConsentGate`
372
-
373
- The component that mounts an embed only while its category is allowed is now
374
- `ConsentGate` in React, Next.js, TanStack Start, Svelte and Vue. The old names
375
- still work as deprecated aliases and will be removed in a later major release.
376
-
377
- | v2 | v3 |
378
- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
379
- | `Frame`, `Frame.Root`, `Frame.Title`, `Frame.Button` | `ConsentGate`, `ConsentGate.Root`, `ConsentGate.Title`, `ConsentGate.Button` |
380
- | `FrameProps`, `FrameCompoundComponent` | `ConsentGateProps`, `ConsentGateCompoundComponent` |
381
- | `c15t/react/frame`, `c15t/react/components/frame` | `c15t/react/consent-gate`, `c15t/react/components/consent-gate` |
382
- | Vue `ConsentFrame`, `@c15t/vue/runtime/components/consent-frame.vue` | `ConsentGate`, `@c15t/vue/runtime/components/consent-gate.vue` |
383
-
384
- Props and behavior are unchanged. The placeholder keeps its `frame.*`
385
- translation keys, `--frame-*` CSS custom properties and
386
- `data-testid="frame-placeholder"`, so custom copy, styles and tests continue
387
- to apply.
388
-
389
- ## Split dialog and widget entries are deferred
390
-
391
- `c15t/react/consent-dialog` and `c15t/react/consent-widget` (and their
392
- `@c15t/react/*` equivalents) now export the same deferred `ConsentDialog` and
393
- `ConsentWidget` as `c15t/react`. The dialog's code loads when it first opens
394
- instead of with every page. `<ConsentDialog />`, `<ConsentWidget />` and the
395
- `ConsentDialog.<Part>` properties need no change.
396
-
397
- The two entries no longer export the individual parts. Import those from the
398
- component entries, which keep the old, eagerly bundled exports:
399
-
400
- | Before | After |
401
- | --------------------------------------------------------------- | -------------------------------------------------------------------------- |
402
- | `import { Card, Header } from 'c15t/react/consent-dialog'` | `import { Card, Header } from 'c15t/react/components/consent-dialog'` |
403
- | `import { Accordion, Switch } from 'c15t/react/consent-widget'` | `import { Accordion, Switch } from 'c15t/react/components/consent-widget'` |
404
-
405
- To keep the dialog in the first load so its first open needs no download,
406
- import `ConsentDialog` from `c15t/react/components/consent-dialog`.
407
-
408
- ## Next.js RSC banner removal
409
-
410
- The `@c15t/nextjs/rsc` entry (`c15t/next/rsc`) and its `RscConsentBanner`,
411
- `RscBannerGate` and `RscBannerActions` exports are gone. Imports of that
412
- entry now fail to resolve. Use the regular banner for server rendering.
413
- The retained benchmark results showed slightly faster banner visibility and
414
- interaction for the experimental shell, so this removal should not be read
415
- as a performance improvement.
416
-
417
- Replace `<RscConsentBanner config={config} />` with `<ConsentBanner />` inside
418
- the same `ConsentRoot`, importing it from `c15t/next` when you use the
419
- umbrella package or from `@c15t/nextjs` when you depend on the scoped package
420
- directly. With an awaited `resolveConsent` result, the server renders
421
- the banner into the response, which is what the RSC variant was for. Move
422
- `presentation` to `options.presentation` on `ConsentRoot`.
423
-
424
- Two defaults differ from the removed shell. It rendered no branding link, so
425
- pass `hideBranding` to keep that. It also styled only its root and action row
426
- (through the stock `ConsentBanner.Root` and `PolicyActions`) and left the
427
- card, title, description, footer and buttons without base classes, whereas
428
- `ConsentBanner` merges the stock styles into every part. If that changes a
429
- custom layout, do not reach for `noStyle` on the whole banner, which also
430
- drops the root positioning and action-row layout the old shell kept; compose
431
- the compound parts instead and pass `noStyle` only to the parts you styled
432
- yourself.
433
-
434
- `ConsentBanner` has no `classNames` or `children` props. Move each legacy
435
- `classNames` key to the `ConsentRoot` `options.components` slots, which take
436
- `{ className }`:
437
-
438
- | `classNames` key | Replacement |
439
- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
440
- | `root`, `card`, `title`, `footer` | `components.banner.root`, `.card`, `.title`, `.footer` |
441
- | `description` | `components.description.banner` |
442
- | `rights`, `rightLink` | `components.banner.rights`, `components.banner.rightLink` |
443
- | `acceptButton`, `rejectButton`, `customizeButton`, `dismissButton` | No per-button slot. Use `components.button.primary` / `.secondary` for shared styles, or target `[data-action="accept"]`, `[data-action="reject"]`, `[data-action="customize"]` and `[data-action="dismiss"]` in CSS. |
444
-
445
- Custom `children`, and any direct use of `RscBannerGate` or
446
- `RscBannerActions`, map to the compound parts of `ConsentBanner` in a Client
447
- Component. `RscBannerGate` becomes `ConsentBanner.Root`, which mounts only
448
- while the policy owes a prompt and reopens on expiry. Remove the gate
449
- `prompt` and `model` props; the root derives `data-prompt` and `data-model`
450
- from the `ConsentRoot` policy. Rename `title` to `aria-label` to preserve its
451
- accessible label. Keep `children`, `className`, `variant`, `position` and
452
- `blocking` on the root. Add `disableAnimation` and `trapFocus={false}` to
453
- retain the old gate defaults.
454
-
455
- `RscBannerActions`
456
- becomes `ConsentBanner.PolicyActions`, which renders the rights links and the
457
- action row the policy requires; `ConsentBanner.Rights`, `RightLink`, `Footer`,
458
- `FooterSubGroup` and the four buttons are available for finer control.
459
- `PolicyActions` takes none of the old `acceptLabel`, `rejectLabel`,
460
- `customizeLabel`, `dismissLabel`, `rightLabels` or `classNames` props: set
461
- labels through the provider `i18n` translation overrides (`common.acceptAll`,
462
- `common.rejectAll`, `common.customize`, `common.acknowledge`, `rights.*`), or
463
- render `ConsentBanner.AcceptButton` and the other buttons with your own
464
- children; `renderAction` on `PolicyActions` replaces one button; classes move
465
- to the `components` slots listed in the table. Place the former `children`
466
- inside `ConsentBanner.Card`:
467
-
468
- ```tsx title="components/banner.tsx"
469
- 'use client';
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
+ ## Keep visitors' existing choices
404
+
405
+ v3 uses the same `c15t` cookie and storage key as v2 and reads v2 records. It
406
+ does not rewrite them on page load; the next accept, reject or save writes a
407
+ v3 record.
408
+
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
412
+ records yourself; c15t decides which v2 choices still count.
413
+
414
+ ## Upgrade a self-hosted backend
415
+
416
+ Deploy the v3 backend and the v3 clients in the same release. A v3 client
417
+ cannot read a v2 backend's `/init` response. Policy resolution fails, optional
418
+ categories stay denied and no banner appears.
419
+
420
+ 1. Install `@c15t/backend@alpha` and the SQL driver for your database.
421
+ 2. Replace `adapter` with `database` in your config. The Drizzle, Prisma,
422
+ TypeORM and Kysely adapters are gone; point `database` at the same SQL
423
+ database instead. MongoDB has no migration path.
424
+ 3. Move `policyPacks`, `branding`, `iab` and `appName` under `manifest`, and
425
+ rename `policyPacks` to `policyRules`.
426
+ 4. Back up the database, then plan and apply the schema migration:
427
+
428
+ ```bash
429
+ npx @c15t/cli@alpha self-host migrate --config ./c15t-backend.config.ts --plan
430
+ npx @c15t/cli@alpha self-host migrate --config ./c15t-backend.config.ts --apply
431
+ ```
432
+
433
+ The migrator recognizes the v2 schema, adopts it, and adds the v3 tables and
434
+ columns. Apply it before the v3 backend serves traffic: v3 records policy
435
+ decisions without the v2 `jurisdiction` label, which the migration makes
436
+ nullable. [Database setup](https://c15t.com/docs/self-host/guides/database-setup#upgrade-a-v2-backend)
437
+ covers the details, and the [backend quickstart](https://c15t.com/docs/self-host/quickstart)
438
+ shows a complete v3 config and route.
439
+
440
+ ### Remove `disableGeoLocation`
441
+
442
+ v3 removes the `disableGeoLocation` manifest option. Policy rules decide by
443
+ country and region. To show every visitor the same banner, configure one rule
444
+ with `match: { isDefault: true }`. It applies wherever the visitor is, so the
445
+ browser can resolve it without asking the backend for a location.
470
446
 
471
- import { ConsentBanner } from 'c15t/next';
472
-
473
- export function Banner() {
474
- return (
475
- <ConsentBanner.Root>
476
- <ConsentBanner.Card>
477
- <ConsentBanner.Header>
478
- <ConsentBanner.Title />
479
- <ConsentBanner.Description />
480
- </ConsentBanner.Header>
481
- <a href="/privacy">Privacy policy</a>
482
- <ConsentBanner.PolicyActions />
483
- </ConsentBanner.Card>
484
- </ConsentBanner.Root>
485
- );
447
+ To test one region's rule from anywhere, set the country in the client's
448
+ `overrides`, for example `overrides: { country: 'US' }`. See
449
+ [runtime options](https://c15t.com/docs/frameworks/javascript/api/runtime).
450
+
451
+ ### Stop reading `jurisdiction`
452
+
453
+ `/init` responses, session reports and `sessions.onReport` no longer carry a
454
+ `jurisdiction` label such as `GDPR`. Read the matched policy from
455
+ `policyResolution` instead, or the report's `policy`, `country` and `region`.
456
+ The backend still accepts `jurisdiction` in a v2 client's save request and
457
+ ignores it.
458
+
459
+ ## Update the Node.js SDK
460
+
461
+ Install `@c15t/node-sdk@alpha`. The v2 client is gone: create one with
462
+ `createC15tClient()` and pass the backend URL and API key yourself.
463
+
464
+ | Package manager | Command |
465
+ | :-------------- | :--------------------------------- |
466
+ | npm | `npm install @c15t/node-sdk@alpha` |
467
+ | pnpm | `pnpm add @c15t/node-sdk@alpha` |
468
+ | yarn | `yarn add @c15t/node-sdk@alpha` |
469
+ | bun | `bun add @c15t/node-sdk@alpha` |
470
+
471
+ ```ts
472
+ import { createC15tClient } from '@c15t/node-sdk';
473
+
474
+ const apiKey = process.env.C15T_API_KEY;
475
+ if (!apiKey) throw new Error('Set C15T_API_KEY');
476
+
477
+ const c15t = createC15tClient({
478
+ baseUrl: 'https://app.example.com/api/c15t',
479
+ apiKey,
480
+ });
481
+
482
+ const result = await c15t.consents.check({
483
+ externalId: 'user_123',
484
+ types: ['privacy_policy', 'marketing_communications'],
485
+ });
486
+ if (result.ok) {
487
+ console.log(result.data.results.privacy_policy.hasConsent);
486
488
  }
487
489
  ```
488
490
 
489
- ## Session reports in manifest mode
490
-
491
- A host that resolves init from a cached manifest never calls `/init`, so a v3
492
- backend could not count the visitors it served. From this release every
493
- server-side resolution sends `POST /sessions` to the backend after the fact,
494
- server-to-server and detached from the response: the Next.js, TanStack Start,
495
- SvelteKit, Nuxt and Astro init routes, and the Next.js, TanStack Start and
496
- Astro render-time prefetches. The browser makes no request.
497
-
498
- This is a new outbound data flow from your server to the consent backend. The
499
- report carries the manifest revision, the matched policy, the jurisdiction,
500
- country, region, language and GPC signal, plus the visitor's user agent and
501
- address as request headers. It carries no cookies and no identifier. Nothing is
502
- stored on the backend; the report reaches a `sessions.onReport` sink and the
503
- request log.
504
-
505
- Reporting is on by default and needs an absolute backend URL. Set
506
- `reportSessions: false` on the adapter to send none. Each report is one
507
- resolution, not one visitor: a page view can produce a `render` report and a
508
- `route` report, and nothing in them links the two. A consumer counting sessions
509
- groups reports by the forwarded address and user agent within a window.
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` |
516
+
517
+ `patchSubject` returned `{ success, subject }`. `subjects.identify` returns
518
+ `{ subject }`, and `subject.identityProvider` is always set. Dates in
519
+ responses, such as `givenAt` and `createdAt`, are now `Date` objects rather
520
+ than strings.
521
+
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).
524
+
525
+ ## Check the migration
526
+
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.
536
+
537
+ The [verification guide](./guides/verify-consent.md) has the full release
538
+ checklist.