@c15t/react 3.1.0 → 3.2.0

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 (146) hide show
  1. package/AGENTS.md +35 -5
  2. package/README.md +2 -2
  3. package/dist/component-hooks/use-consent-gate.js +2 -0
  4. package/dist/component-hooks/use-consent-labels.js +2 -0
  5. package/dist/component-hooks/use-consent-text-direction.js +2 -0
  6. package/dist/component-hooks/use-vendors-listed-under.js +2 -0
  7. package/dist/components/consent-gate/atoms.js +1 -1
  8. package/dist/components/consent-gate/consent-gate.js +1 -1
  9. package/dist/components/iab-prompt/atoms/root.js +1 -1
  10. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  11. package/dist/components/preferences/atoms/button.js +1 -1
  12. package/dist/components/preferences/atoms/vendor-list.js +1 -1
  13. package/dist/components/prompt/atoms/root.js +1 -1
  14. package/dist/components/prompt/components.js +1 -1
  15. package/dist/components/prompt/policy-actions.js +1 -1
  16. package/dist/components/prompt/prompt.js +1 -1
  17. package/dist/components/prompt/surface-context.js +1 -1
  18. package/dist/components/prompt/use-banner-copy.js +1 -1
  19. package/dist/components/shared/streamed-surface.js +1 -1
  20. package/dist/headless.js +1 -1
  21. package/dist/index-parts/offline.js +1 -1
  22. package/dist/modes.js +1 -1
  23. package/dist/styles.tw3.css +2 -2
  24. package/dist/transports/offline.js +1 -1
  25. package/dist/version.js +1 -1
  26. package/dist-types/component-hooks/use-consent-gate.d.ts +102 -0
  27. package/dist-types/component-hooks/use-consent-labels.d.ts +32 -0
  28. package/dist-types/component-hooks/use-consent-text-direction.d.ts +22 -0
  29. package/dist-types/component-hooks/use-vendors-listed-under.d.ts +30 -0
  30. package/dist-types/components/prompt/atoms/root.d.ts +3 -3
  31. package/dist-types/components/prompt/prompt.d.ts +4 -3
  32. package/dist-types/components/prompt/surface-context.d.ts +1 -14
  33. package/dist-types/components/prompt/use-banner-copy.d.ts +8 -3
  34. package/dist-types/components/shared/streamed-surface.d.ts +47 -10
  35. package/dist-types/headless.d.ts +10 -1
  36. package/dist-types/hooks/use-iab-policy-guard.d.ts +17 -1
  37. package/dist-types/hooks/use-scroll-lock.d.ts +16 -2
  38. package/dist-types/index-parts/offline.d.ts +1 -1
  39. package/dist-types/modes.d.ts +1 -1
  40. package/dist-types/module-hooks/script-loader.d.ts +9 -0
  41. package/dist-types/provider.d.ts +3 -3
  42. package/dist-types/transports/offline.d.ts +2 -2
  43. package/dist-types/version.d.ts +1 -1
  44. package/docs/README.md +35 -5
  45. package/docs/concepts/choose-your-setup.md +9 -9
  46. package/docs/concepts/consent-state.md +81 -9
  47. package/docs/concepts/data-fetching.md +30 -23
  48. package/docs/concepts/modes.md +87 -35
  49. package/docs/customization/recipes.md +4 -4
  50. package/docs/customization/stylesheets.md +42 -3
  51. package/docs/customization/tailwind.md +17 -34
  52. package/docs/customization/translations.md +18 -12
  53. package/docs/frameworks/react/clear-on-revocation.md +3 -2
  54. package/docs/frameworks/react/components/consent-banner.md +3 -3
  55. package/docs/frameworks/react/components/consent-provider.md +29 -29
  56. package/docs/frameworks/react/gpp.md +1 -1
  57. package/docs/frameworks/react/headless.md +146 -19
  58. package/docs/frameworks/react/hooks.md +2 -1
  59. package/docs/frameworks/react/iab.md +8 -1
  60. package/docs/frameworks/react/quickstart.md +2 -2
  61. package/docs/frameworks/react/rendering.md +1 -1
  62. package/docs/frameworks/react/translations.md +9 -9
  63. package/docs/frameworks/react/troubleshooting.md +1 -1
  64. package/docs/frameworks/react/upgrade-v3.md +39 -32
  65. package/docs/frameworks/react/vendor-consent.md +5 -0
  66. package/docs/guides/banner-experiments.md +3 -3
  67. package/docs/guides/troubleshooting.md +3 -2
  68. package/docs/integrations/activecampaign.md +297 -0
  69. package/docs/integrations/ada.md +396 -0
  70. package/docs/integrations/adobe-analytics.md +2 -2
  71. package/docs/integrations/adobe-target.md +460 -0
  72. package/docs/integrations/adroll.md +336 -0
  73. package/docs/integrations/ahrefs-analytics.md +2 -2
  74. package/docs/integrations/amazon-ads.md +424 -0
  75. package/docs/integrations/amplitude.md +2 -2
  76. package/docs/integrations/appcues.md +368 -0
  77. package/docs/integrations/attentive.md +313 -0
  78. package/docs/integrations/building-integrations.md +29 -16
  79. package/docs/integrations/clay.md +305 -0
  80. package/docs/integrations/clearbit.md +3 -3
  81. package/docs/integrations/cloudflare-web-analytics.md +2 -2
  82. package/docs/integrations/cloudflare-zaraz.md +2 -2
  83. package/docs/integrations/crisp.md +2 -2
  84. package/docs/integrations/customer-io.md +369 -0
  85. package/docs/integrations/databuddy.md +127 -52
  86. package/docs/integrations/default-com.md +497 -0
  87. package/docs/integrations/dynamic-yield.md +373 -0
  88. package/docs/integrations/fathom-analytics.md +2 -2
  89. package/docs/integrations/front-chat.md +2 -2
  90. package/docs/integrations/fullstory.md +298 -0
  91. package/docs/integrations/gainsight-px.md +418 -0
  92. package/docs/integrations/google-maps.md +2 -2
  93. package/docs/integrations/google-tag-manager.md +59 -2
  94. package/docs/integrations/google-tag.md +2 -2
  95. package/docs/integrations/gorgias.md +365 -0
  96. package/docs/integrations/heap.md +2 -2
  97. package/docs/integrations/hightouch.md +2 -2
  98. package/docs/integrations/hotjar.md +40 -26
  99. package/docs/integrations/hubspot.md +529 -0
  100. package/docs/integrations/insider.md +550 -0
  101. package/docs/integrations/intercom.md +2 -2
  102. package/docs/integrations/klaviyo.md +2 -2
  103. package/docs/integrations/linkedin-insights.md +2 -2
  104. package/docs/integrations/liveintent.md +327 -0
  105. package/docs/integrations/logrocket.md +2 -2
  106. package/docs/integrations/lucky-orange.md +336 -0
  107. package/docs/integrations/marketo.md +309 -0
  108. package/docs/integrations/matomo-analytics.md +2 -2
  109. package/docs/integrations/meta-pixel.md +2 -2
  110. package/docs/integrations/microsoft-clarity.md +2 -2
  111. package/docs/integrations/microsoft-uet.md +2 -2
  112. package/docs/integrations/mixpanel-analytics.md +2 -2
  113. package/docs/integrations/one-dollar-stats.md +2 -2
  114. package/docs/integrations/openai-pixel.md +2 -2
  115. package/docs/integrations/optimizely.md +411 -0
  116. package/docs/integrations/oracle-eloqua.md +301 -0
  117. package/docs/integrations/overview.md +128 -81
  118. package/docs/integrations/pendo.md +339 -0
  119. package/docs/integrations/pinterest-tag.md +2 -2
  120. package/docs/integrations/pirsch.md +2 -2
  121. package/docs/integrations/plausible-analytics.md +2 -2
  122. package/docs/integrations/posthog.md +2 -2
  123. package/docs/integrations/promptwatch.md +2 -2
  124. package/docs/integrations/rb2b.md +363 -0
  125. package/docs/integrations/reddit-pixel.md +2 -2
  126. package/docs/integrations/rudderstack.md +2 -2
  127. package/docs/integrations/rybbit-analytics.md +2 -2
  128. package/docs/integrations/salesforce-pardot.md +342 -0
  129. package/docs/integrations/segment.md +2 -2
  130. package/docs/integrations/sentry.md +2 -2
  131. package/docs/integrations/shopify.md +323 -0
  132. package/docs/integrations/snapchat-pixel.md +2 -2
  133. package/docs/integrations/statable-analytics.md +345 -0
  134. package/docs/integrations/the-trade-desk.md +328 -0
  135. package/docs/integrations/tiktok-pixel.md +2 -2
  136. package/docs/integrations/umami-analytics.md +2 -2
  137. package/docs/integrations/vector.md +333 -0
  138. package/docs/integrations/vercel-analytics.md +2 -2
  139. package/docs/integrations/vercel-speed-insights.md +7 -2
  140. package/docs/integrations/wisepops.md +416 -0
  141. package/docs/integrations/x-pixel.md +2 -2
  142. package/docs/integrations/youtube.md +2 -2
  143. package/docs/integrations/zendesk.md +359 -0
  144. package/package.json +10 -32
  145. package/dist/components/prompt/resolve-banner-primary-actions.js +0 -1
  146. package/dist-types/components/prompt/resolve-banner-primary-actions.d.ts +0 -14
@@ -0,0 +1,418 @@
1
+ ---
2
+ title: Gainsight PX
3
+ description: Gate Gainsight PX product analytics and in-app engagements on
4
+ consent, configure the aptrinsic snippet and disable tracking when consent is
5
+ withdrawn.
6
+ seoTitle: Gainsight PX cookie consent with c15t
7
+ seoDescription: Load Gainsight PX after analytics consent. Configure user and
8
+ account identity, choose a consent category and disable the tag when consent
9
+ is withdrawn.
10
+ icon: chart-no-axes-combined
11
+ group: integrations
12
+ ---
13
+
14
+ The `gainsightPx` helper waits for `measurement` consent before it creates
15
+ `window.aptrinsic` or requests the PX SDK. This gate covers both product
16
+ analytics and in-app engagements. On revocation, the helper disables the tag
17
+ through PX's config API and requests a session reset.
18
+
19
+ ## Configure Gainsight PX
20
+
21
+ Copy the product/channel key from **Administration > Products** in Gainsight PX.
22
+ Remove any existing PX snippet or tag-manager installation so it cannot load
23
+ outside c15t.
24
+
25
+ | Package manager | Command |
26
+ | :-------------- | :------------------------------- |
27
+ | npm | `npm install @c15t/integrations` |
28
+ | pnpm | `pnpm add @c15t/integrations` |
29
+ | yarn | `yarn add @c15t/integrations` |
30
+ | bun | `bun add @c15t/integrations` |
31
+
32
+ ```ts title="src/consent-scripts.ts"
33
+ import { gainsightPx } from '@c15t/integrations/gainsight-px';
34
+
35
+ export const scripts = [gainsightPx({ productKey: 'AP-PRODUCT-KEY-2' })];
36
+ ```
37
+
38
+ ## Register the scripts
39
+
40
+ Complete your [framework quickstart](https://c15t.com/docs/frameworks) first. Keep its Inth
41
+ endpoint, policy, styles and consent UI. Remove the vendor's original script,
42
+ SDK initializer or tag-manager entry, so the vendor loads only through c15t.
43
+
44
+ The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
45
+ quickstart already has a `scripts` array, such as the one in `c15t.config.ts`
46
+ in the Next.js guide, add the helper to that array instead of creating a
47
+ second file.
48
+ The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
49
+ point for your framework below. These are edits to that provider, not a second
50
+ provider.
51
+
52
+ **Next.js**
53
+
54
+ Add the configuration to `scripts` in `c15t.config.ts`, next to
55
+ `next.config.ts`:
56
+
57
+ ```ts title="c15t.config.ts"
58
+ import { defineConsentConfig } from 'c15t/next';
59
+ import { scripts } from './src/consent-scripts';
60
+
61
+ export default defineConsentConfig({ scripts });
62
+ ```
63
+
64
+ Keep the rest of your config, such as `mode` and `routePrefix`, in the
65
+ same call. `ConsentRoot` reads the config in the browser, so the layout
66
+ keeps passing only `state`. App Router, Pages Router and static export all
67
+ read the same file. See
68
+ [Next.js scripts and embeds](https://c15t.com/docs/frameworks/next/scripts).
69
+
70
+ **TanStack Start**
71
+
72
+ Import the configuration into your root route and pass it to the existing
73
+ `ConsentRoot` as a top-level prop. Keep the loader from the
74
+ [TanStack Start quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart):
75
+
76
+ ```tsx title="src/routes/__root.tsx"
77
+ import { scripts } from '../consent-scripts';
78
+
79
+ <ConsentRoot state={consent} scripts={scripts}>
80
+ ```
81
+
82
+ Import vendor helpers in the root route module, not in a server function.
83
+ A server function's return value must be serializable, and script
84
+ configurations carry callbacks. See
85
+ [TanStack Start scripts](https://c15t.com/docs/frameworks/tanstack-start/scripts).
86
+
87
+ **React**
88
+
89
+ Add the configuration to the existing `ConsentProvider` options, next to
90
+ `mode`:
91
+
92
+ ```tsx title="src/consent.tsx"
93
+ import { scripts } from './consent-scripts';
94
+
95
+ <ConsentProvider options={{ mode, scripts }}>
96
+ ```
97
+
98
+ `mode` is the `manifest()` value from the
99
+ [React quickstart](../frameworks/react/quickstart.md). Keep the banner,
100
+ dialog and preferences link inside the provider. See
101
+ [React scripts and embeds](../frameworks/react/scripts.md).
102
+
103
+ **Nuxt**
104
+
105
+ Register the scripts under the `c15t` key in `app/app.config.ts`. Adjust the
106
+ relative import to where you created `consent-scripts.ts`:
107
+
108
+ ```ts title="app/app.config.ts"
109
+ import { scripts } from '../src/consent-scripts';
110
+
111
+ export default defineAppConfig({
112
+ c15t: { scripts },
113
+ });
114
+ ```
115
+
116
+ The Nuxt module merges this over its options in `nuxt.config.ts` and starts
117
+ one script loader in the browser after hydration, once it has applied the
118
+ visitor's stored choice and privacy signals. Keep `scripts` out of
119
+ `nuxt.config.ts`, which reaches the browser as JSON and drops the vendor
120
+ callbacks. Write the vendor IDs into `consent-scripts.ts`. See
121
+ [Nuxt scripts and embeds](https://c15t.com/docs/frameworks/nuxt/scripts).
122
+
123
+ **Vue**
124
+
125
+ Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:
126
+
127
+ ```ts title="src/main.ts"
128
+ import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
129
+ import { scripts } from './consent-scripts';
130
+
131
+ app.use(c15tVue, {
132
+ mode: manifest(),
133
+ scripts,
134
+ });
135
+ ```
136
+
137
+ Keep your existing `mode` and other options. The plugin starts one
138
+ script loader when the app mounts, after it has applied the visitor's stored
139
+ choice. Do not also call `createScriptLoader` from a component. See
140
+ [Vue scripts and embeds](https://c15t.com/docs/frameworks/vue/scripts).
141
+
142
+ **Astro**
143
+
144
+ Add the scripts to the client entrypoint from the
145
+ [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), `src/c15t.client.ts`,
146
+ which the integration finds on its own. Keep `astro.config.mjs` as it is.
147
+ If the module already exports scripts, combine the two arrays.
148
+
149
+ ```ts title="src/c15t.client.ts"
150
+ import type { C15tClientOptionsExtension } from 'c15t/astro';
151
+ import { scripts } from './consent-scripts';
152
+
153
+ export default { scripts } satisfies C15tClientOptionsExtension;
154
+ ```
155
+
156
+ Vendor helpers contain callbacks, and the integration options in
157
+ `astro.config.mjs` are serialized into the page, so do not put helpers in
158
+ the integration's `scripts` option. The integration passes the client
159
+ entrypoint to the one runtime every page shares, including across
160
+ `ClientRouter` navigation.
161
+
162
+ **Svelte**
163
+
164
+ Import the scripts in the component that owns your existing provider and
165
+ pass them as a top-level prop:
166
+
167
+ ```svelte title="src/App.svelte"
168
+ <script lang="ts">
169
+ import { ConsentProvider, manifest } from 'c15t/svelte';
170
+ import { scripts } from './consent-scripts';
171
+ </script>
172
+
173
+ <ConsentProvider mode={manifest()} {scripts}>
174
+ <!-- Keep your application, consent UI and preferences link here. -->
175
+ </ConsentProvider>
176
+ ```
177
+
178
+ Retain the styles and consent UI from the [Svelte quickstart](https://c15t.com/docs/frameworks/svelte/quickstart).
179
+ The provider owns the loader and disposes it on unmount.
180
+
181
+ **SvelteKit**
182
+
183
+ Add the scripts to the existing `ConsentRoot` in the root layout. Keep the
184
+ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart).
185
+
186
+ ```svelte title="src/routes/+layout.svelte"
187
+ <script lang="ts">
188
+ import { ConsentRoot } from 'c15t/svelte';
189
+ import { scripts } from '../consent-scripts';
190
+
191
+ let { children, data } = $props();
192
+ </script>
193
+
194
+ <ConsentRoot state={data.consent} {scripts}>
195
+ {@render children()}
196
+ <!-- Keep your consent UI and preferences link here. -->
197
+ </ConsentRoot>
198
+ ```
199
+
200
+ Import vendor helpers in the layout component, not in `+layout.server.ts`:
201
+ a server load cannot send functions to the browser. Prerendered, static and
202
+ SPA-mode pages use the same `scripts` prop. If you pass an externally owned
203
+ `runtime` to the provider, register scripts when creating that runtime instead.
204
+
205
+ **HTML**
206
+
207
+ The helpers in `@c15t/integrations` are ES modules that need a bundler. On a
208
+ page that loads the c15t script tag, paste the vendor's own snippet instead
209
+ and keep it inert until its category is allowed:
210
+
211
+ ```html
212
+ <script type="text/plain" data-c15t-category="measurement">
213
+ // The vendor's snippet, unchanged
214
+ </script>
215
+ ```
216
+
217
+ Use the category this guide names for the vendor. c15t runs the snippet
218
+ once that category is allowed, and reloads the page when the visitor
219
+ withdraws it. Helper options on this page, such as `loadMode`, do not apply
220
+ to a pasted snippet. See [HTML scripts](https://c15t.com/docs/frameworks/html/scripts).
221
+
222
+ **JavaScript**
223
+
224
+ Pass the scripts to `init()` from `@c15t/browser`, next to your mode:
225
+
226
+ ```ts
227
+ import { init, manifest } from '@c15t/browser';
228
+ import { scripts } from './consent-scripts';
229
+
230
+ const consent = init({
231
+ mode: manifest(),
232
+ scripts,
233
+ });
234
+ ```
235
+
236
+ Keep the mode from your quickstart. With
237
+ `createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
238
+ A kernel you create yourself needs a loader from
239
+ `c15t/modules/script-loader`. Attach one loader per kernel. See
240
+ [JavaScript scripts](https://c15t.com/docs/frameworks/javascript/scripts).
241
+
242
+ ## Options
243
+
244
+ | Option | Default | Behavior |
245
+ | ------------ | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
246
+ | `productKey` | Required | Product/channel key assigned to `aptrinsic.p` and the loader's `a` query parameter. Changing the key after this helper starts PX throws an error and requires a page reload. Blank values log an error and skip installation. |
247
+ | `category` | `measurement` | Consent category or condition for the whole SDK. Choose `experience` if it fits your use of analytics and engagements. |
248
+ | `config` | None | JSON-serializable PX startup configuration assigned to `aptrinsic.c`. Changing it after PX starts throws and requires a page reload. c15t owns `enableTag`; do not set it yourself. |
249
+ | `user` | None | User identify payload with a required `id`. The helper queues it only after mapped consent. |
250
+ | `account` | None | Optional account identify payload with a required `id`. Sent only when `user` is also supplied. |
251
+ | `scriptUrl` | `https://web-sdk.aptrinsic.com/api/aptrinsic.js?a=<productKey>` | Full loader override. Other PX requests still use the endpoints configured by the SDK. |
252
+
253
+ `config` supports PX options such as `fullDomainCookie`, `localStorageCookie`,
254
+ `usePOST` and `htmlSanitization`. Keep configuration and identity data
255
+ JSON-serializable. Send dates as epoch milliseconds. For custom attributes,
256
+ use the API names from **Administration > Attributes**.
257
+
258
+ ```ts
259
+ // Options for gainsightPx in src/consent-scripts.ts.
260
+ {
261
+ productKey: 'AP-PRODUCT-KEY-2',
262
+ category: 'experience',
263
+ config: { fullDomainCookie: true },
264
+ user: { id: 'user-123', plan: 'paid' },
265
+ account: { id: 'account-456' },
266
+ }
267
+ ```
268
+
269
+ The optional identity is part of the script configuration. For a later login or
270
+ account change, call PX's `identify` API only while the visitor still permits
271
+ this vendor. c15t does not gate event calls made elsewhere in your application.
272
+
273
+ ## Loading and revocation
274
+
275
+ Before mapped consent, the helper creates no PX global, loader, cookies or
276
+ localStorage entries. It does not load PX with `enableTag: false` as an
277
+ early-loading mode.
278
+
279
+ After consent, the helper creates the official function queue with `p` for the
280
+ product key and `c` for configuration. It enables the tag, optionally queues
281
+ `identify`, then appends the asynchronous loader. PX may begin anonymous
282
+ tracking before its queued identify call completes.
283
+
284
+ When mapped consent is withdrawn, the helper sends
285
+ `aptrinsic('config', 'enableTag', false)` followed by `aptrinsic('reset')` and
286
+ removes the loader element if it is still downloading or failed to load.
287
+ After the loader's load event, the helper retains the element. Gainsight
288
+ documents `enableTag: false` as stopping PX cookies, network calls and
289
+ engagements. Its current SDK resets the session
290
+ inside the disable call and ignores a separate `reset` while disabled.
291
+
292
+ If `reloadOnConsentRevoked` is disabled, granting consent again after a completed
293
+ load sends `aptrinsic('config', 'enableTag', true)` and repeats the optional
294
+ `identify` call through the existing API. The helper reuses the loaded SDK
295
+ because PX rejects a second execution of its bundle. A loader removed before
296
+ completion can be requested again on a later grant if PX has not started
297
+ executing it.
298
+
299
+ Before requesting another pending load, the helper rebuilds its function queue
300
+ from the current options. The new queue contains only the current product,
301
+ configuration and optional identity, so an interrupted setup cannot replay an
302
+ earlier user's identify call. This also applies to server-generated startup.
303
+ It does not cancel a detached download that may still execute later.
304
+
305
+ When hydrating a server-started loader, use the same
306
+ `productKey`, `config`, `user`, `account` and `scriptUrl` on the server and client.
307
+ The loader compares the server setup with the client options before adopting
308
+ the server's element. This check also runs when PX finishes startup before
309
+ hydration. c15t retains the startup settings separately from PX's queue so the
310
+ check still works after PX replaces its function stub. A mismatch throws before
311
+ registration or replacement and leaves the existing setup intact. If identity
312
+ changes between render and hydration, reload the page with the current options.
313
+ Matching options preserve the existing SDK and loader. Equivalent configuration
314
+ objects can use different property orders.
315
+
316
+ Disposing the last provider or script loader removes its element, but PX's code
317
+ remains in the page. Later mounts check PX's own bundle execution marker and
318
+ use the existing API without requesting the bundle again. A denied mount sends
319
+ disable and reset commands without enabling PX or identifying the user. A later
320
+ grant enables the tag and repeats the optional identify call. An `aptrinsic`
321
+ queue alone does not prove that the SDK ran. The runtime check also survives
322
+ customization with object spread, such as
323
+ `{ ...gainsightPx(options), anonymizeId: false }`, even when the copy was made
324
+ before PX started and the same copy is mounted again after disposal.
325
+ Creating a fresh spread copy with equivalent options on each rerender also
326
+ preserves a pending loader and its queued setup.
327
+
328
+ Once this helper starts the SDK, use the same `productKey` for the rest of the
329
+ page's lifetime, including after disposal. Constructing a helper with another
330
+ key throws an error. The loader also rejects a different-key script created
331
+ before startup if it is registered after PX starts, preserving the current
332
+ configuration and its consent handling. Reload the page before installing a
333
+ different product.
334
+ PX's asynchronous `tagKey` API queues commands during reconfiguration and can
335
+ defer opt-out until after queued identity calls, so c15t does not use it.
336
+
337
+ Keep the same `config` after startup too, including after disposal. Adding,
338
+ removing or changing a configuration value throws before registration or
339
+ replacement, so the current consent handling stays active. This also applies
340
+ to scripts created before startup and registered afterward. Equivalent objects
341
+ may use a different property order. The reuse path forwards consent and optional
342
+ identity calls; it does not apply new startup settings through PX's config API.
343
+ Reload the page to change configuration.
344
+
345
+ PX has one global SDK per window. Mount one configuration at a time. Owners
346
+ with different options still control the same SDK and do not share a consent
347
+ veto. Equivalent configurations can share owners.
348
+
349
+ Disposal itself does not disable PX or stop its timers and listeners. Keep a
350
+ consent owner mounted while the SDK is in use so it can forward revocation.
351
+
352
+ Keep `reloadOnConsentRevoked` enabled. Removing a pending loader does not unload
353
+ PX's code or cancel requests that already started. A load that finishes after
354
+ revocation may still execute vendor code. The helper queues the stop request,
355
+ but a page reload ends the SDK's timers and listeners. If consent returns before
356
+ that detached download executes, a new loader can race with it and PX can reject
357
+ the duplicate bundle. This helper does not
358
+ guarantee that every PX feature resumes after another grant without a reload.
359
+ Verify recovery with your product key, SDK version and enabled features.
360
+
361
+ ## Cookies and storage
362
+
363
+ Gainsight documents these first-party cookies:
364
+
365
+ | Name | Lifetime | Purpose |
366
+ | --------- | ------------------------ | ---------------------------- |
367
+ | `apt.uid` | 1 year | Persistent visitor identity. |
368
+ | `apt.sid` | 30 minutes of inactivity | Current session identity. |
369
+
370
+ `fullDomainCookie: true` scopes cookie data to the full hostname rather than
371
+ the top-level domain. `localStorageCookie: true` moves cookie data to
372
+ localStorage. These settings change where PX persists data; they do not replace
373
+ the consent gate. Inspect storage for your configured PX version and features.
374
+
375
+ ## Verify Gainsight PX
376
+
377
+ In a private window, deny analytics and confirm there is no request to
378
+ `web-sdk.aptrinsic.com` or `esp.aptrinsic.com`, no `window.aptrinsic`, and no
379
+ `apt.uid` or `apt.sid` storage. Allow the category selected by `category`, then
380
+ check that the SDK loads and your PX product receives the expected events.
381
+ Revoke that category and confirm the reloaded page makes no PX request.
382
+
383
+ The helper already sends PX's documented opt-out command. The shared advice
384
+ about adding a custom stop callback applies when you need more cleanup than
385
+ this command provides.
386
+
387
+ Test in a private window with an opt-in policy. Open DevTools Network, disable
388
+ the cache and filter by the vendor's domain:
389
+
390
+ 1. Load the page. No request goes to the vendor before you choose.
391
+ 2. Click Reject, then reload. There is still no vendor request.
392
+ 3. Open Privacy settings and allow the helper's category. The vendor script
393
+ loads without a page reload.
394
+ 4. Turn the category off again and save. c15t reloads the page, and the new
395
+ page makes no vendor request.
396
+
397
+ c15t reloads on revocation because removing a script element does not stop
398
+ code that already ran. The vendor's listeners, timers and queued events stay
399
+ alive until the page unloads. If you set `reloadOnConsentRevoked: false`, stop
400
+ the vendor yourself. Register a callback-only script whose `onConsentChange`
401
+ calls the vendor's opt-out API, as shown in
402
+ [custom integrations](./building-integrations.md), and check the
403
+ permission before each of your own event calls. The reload does not delete
404
+ cookies the vendor already set; see
405
+ [clear on revocation for your framework](./overview.md#vendor-switches-and-cookie-cleanup).
406
+
407
+ The helper lists its vendor by name in the preference dialog, so a visitor can
408
+ turn it off inside an allowed category. See
409
+ [vendor consent for your framework](./overview.md#vendor-switches-and-cookie-cleanup). The
410
+ [consent verification guide](../guides/verify-consent.md) covers navigation,
411
+ expiry and hosting checks.
412
+
413
+ ## Official sources
414
+
415
+ * [PX Web SDK snippet, identify and reset](https://support.gainsight.com/PX/API_for_Developers/01About/Work_with_Gainsight_PX_Web_SDK)
416
+ * [Config API and tracking opt-out](https://support.gainsight.com/PX/API_for_Developers/02Usage_of_Different_APIs/Use_Config_API)
417
+ * [JavaScript SDK configuration](https://support.gainsight.com/PX/Administration/General/Javascript_SDK_Configuration)
418
+ * [PX analytics cookies](https://support.gainsight.com/PX/Install_PX/Install_PX_Web/Analytics_Cookies)
@@ -239,7 +239,7 @@ The provider from your quickstart supplies its consent state.
239
239
 
240
240
  ```svelte title="src/ConsentEmbed.svelte"
241
241
  <script lang="ts">
242
- import { ConsentGate } from '@c15t/svelte';
242
+ import { ConsentGate } from 'c15t/svelte';
243
243
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from './embed-config';
244
244
  </script>
245
245
 
@@ -268,7 +268,7 @@ Keep the SvelteKit root and its server-resolved state unchanged.
268
268
 
269
269
  ```svelte title="src/lib/ConsentEmbed.svelte"
270
270
  <script lang="ts">
271
- import { ConsentGate } from '@c15t/svelte';
271
+ import { ConsentGate } from 'c15t/svelte';
272
272
  import { embedCategory, embedURL, embedTitle, embedAspectRatio } from '../embed-config';
273
273
  </script>
274
274
 
@@ -175,7 +175,7 @@ pass them as a top-level prop:
175
175
 
176
176
  ```svelte title="src/App.svelte"
177
177
  <script lang="ts">
178
- import { ConsentProvider, manifest } from '@c15t/svelte';
178
+ import { ConsentProvider, manifest } from 'c15t/svelte';
179
179
  import { scripts } from './consent-scripts';
180
180
  </script>
181
181
 
@@ -194,7 +194,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
194
194
 
195
195
  ```svelte title="src/routes/+layout.svelte"
196
196
  <script lang="ts">
197
- import { ConsentRoot } from '@c15t/svelte';
197
+ import { ConsentRoot } from 'c15t/svelte';
198
198
  import { scripts } from '../consent-scripts';
199
199
 
200
200
  let { children, data } = $props();
@@ -356,6 +356,63 @@ settings, or fire it from a Custom Event trigger on `consent-update`. If a
356
356
  vendor must make no request before consent, remove it from the container and
357
357
  register its own c15t helper instead.
358
358
 
359
+ ## Use the GTM template instead
360
+
361
+ Use the `googleTagManager` helper where you can. It sends the Consent Mode
362
+ default before `gtm.js` starts, and it can hold the container back until
363
+ consent. The c15t Consent Management template in the GTM Community Template
364
+ Gallery is for sites that manage scripts only through GTM. It loads the c15t
365
+ script-tag build, `@c15t/browser`, from inside the container and forwards
366
+ c15t's permissions to Google. Use the helper or the template on a page, never
367
+ both, or Google receives two sets of consent commands.
368
+
369
+ To add it:
370
+
371
+ 1. In GTM, open Templates, search the gallery for **c15t Consent
372
+ Management** and add it. If it is not listed yet, download
373
+ [`template.tpl`](https://github.com/c15t/c15t/blob/canary/packages/browser/gtm-template/template.tpl),
374
+ then under Tag Templates choose New, open the ⋮ menu, choose Import and
375
+ select the file.
376
+ 2. Create a tag from it with the **Consent Initialization - All Pages**
377
+ trigger, so it runs before your other tags.
378
+ 3. Set **c15t script URL** to one of:
379
+ * `https://your-project.inth.app/c15t.js` for an Inth project, which
380
+ serves the script with its backend URL already set.
381
+ * `https://cdn.jsdelivr.net/npm/@c15t/browser@<version>/dist/c15t.js`,
382
+ with **Backend URL** set to your backend. The tag fails without it.
383
+ * `https://cdn.jsdelivr.net/npm/@c15t/browser@<version>/dist/c15t.local.js`
384
+ for local mode, with **Backend URL** left blank. This file needs
385
+ version 3.2.0 or later; earlier versions call it `c15t.offline.js`.
386
+ 4. Under **Consent categories**, tick each purpose your container's tags
387
+ serve. c15t asks the visitor about these, besides strictly necessary.
388
+ Analytics and Marketing are ticked by default. With none ticked, c15t
389
+ asks only about categories that your c15t setup sets, and a container
390
+ loaded only through this tag has none.
391
+ 5. Leave **Default consent state** empty to deny every type except
392
+ `security_storage`, or add rows per region. If you add region rows, also
393
+ add a row with **Region** blank. Without it, visitors outside those regions
394
+ get no default from the tag. **Wait for update** defaults to 500
395
+ milliseconds.
396
+
397
+ Use an exact version, such as `3.1.0`. The template rejects tags such as
398
+ `latest` and ranges such as `3.1`, which jsDelivr would move to each new
399
+ release without a change to your container. It accepts only Inth and jsDelivr
400
+ URLs. For a self-hosted backend,
401
+ add its script tag to your page source and untick **Load c15t from this tag**.
402
+ The tag then subscribes to the c15t already on the page. It also does this
403
+ when it finds c15t loaded, whichever setting you choose.
404
+
405
+ Once c15t starts, the tag sends an `update` with the visitor's permissions,
406
+ then pushes a `consent-update` event. It does both again after each change. It
407
+ uses the same category mapping as the helper, shown above, and cannot change
408
+ it. Unlike the helper, it also pushes the event when c15t starts, because c15t
409
+ usually starts after your page-view triggers have fired.
410
+
411
+ A tag that its consent settings block does not fire again by itself when the
412
+ visitor allows it. Give each non-Google tag a consent requirement and a Custom
413
+ Event trigger on `consent-update`. If the tag should fire once per page, set
414
+ its firing option to Once per page.
415
+
359
416
  ## Measure opt-in rate
360
417
 
361
418
  If you run a banner experiment, the backend already counts visitors and
@@ -172,7 +172,7 @@ pass them as a top-level prop:
172
172
 
173
173
  ```svelte title="src/App.svelte"
174
174
  <script lang="ts">
175
- import { ConsentProvider, manifest } from '@c15t/svelte';
175
+ import { ConsentProvider, manifest } from 'c15t/svelte';
176
176
  import { scripts } from './consent-scripts';
177
177
  </script>
178
178
 
@@ -191,7 +191,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
191
191
 
192
192
  ```svelte title="src/routes/+layout.svelte"
193
193
  <script lang="ts">
194
- import { ConsentRoot } from '@c15t/svelte';
194
+ import { ConsentRoot } from 'c15t/svelte';
195
195
  import { scripts } from '../consent-scripts';
196
196
 
197
197
  let { children, data } = $props();