@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,550 @@
1
+ ---
2
+ title: Insider One
3
+ description: Load Insider One after consent with current user attributes,
4
+ currency and page context through the c15t insiderTag helper.
5
+ seoTitle: Insider One cookie consent with c15t
6
+ seoDescription: Load Insider One after marketing consent. Send gdpr_optin on
7
+ grant and revocation, set page context, and check requests and storage with
8
+ c15t.
9
+ icon: insider
10
+ group: integrations
11
+ ---
12
+
13
+ Insider One runs personalized web campaigns and builds customer profiles from
14
+ on-site behavior. The c15t `insiderTag` helper waits for `marketing` consent
15
+ before it creates `InsiderQueue` or requests Insider's `ins.js`. On grant, it
16
+ queues the current user with `gdpr_optin: true`, an optional currency, the page
17
+ definition and `init`, then adds the loader.
18
+
19
+ On withdrawal, it queues `gdpr_optin: false`, a neutral `{ type: 'other' }`
20
+ page and `init`, then removes the loader. Insider has no unload method, so keep
21
+ c15t's default reload on revocation to stop code the SDK already ran.
22
+
23
+ ## Configure Insider One
24
+
25
+ Ask Insider for your public `partnerName` and `partnerId`, and confirm that
26
+ Web SDK integration is turned on for your account. Remove any existing Insider
27
+ tag or tag-manager installation so it can't load outside c15t's consent gate.
28
+ Also remove any code that defines or fills `window.insider_object`. Insider
29
+ doesn't support that legacy integration alongside `InsiderQueue`.
30
+
31
+ Insider says to keep the tag off pages with sensitive information, such as
32
+ payment pages with card data.
33
+
34
+ | Package manager | Command |
35
+ | :-------------- | :------------------------------- |
36
+ | npm | `npm install @c15t/integrations` |
37
+ | pnpm | `pnpm add @c15t/integrations` |
38
+ | yarn | `yarn add @c15t/integrations` |
39
+ | bun | `bun add @c15t/integrations` |
40
+
41
+ ```ts title="src/consent-scripts.ts"
42
+ import { insiderTag } from '@c15t/integrations/insider';
43
+
44
+ export const scripts = [
45
+ insiderTag({
46
+ currency: 'GBP',
47
+ pageType: {
48
+ type: 'category',
49
+ value: { breadcrumb: ['Clothing', 'Dresses'] },
50
+ },
51
+ partnerId: 'YOUR_PARTNER_ID',
52
+ partnerName: 'yourpartner',
53
+ }),
54
+ ];
55
+ ```
56
+
57
+ This example describes a clothing category page. Replace its `pageType` with
58
+ the definition for the current page. Use `{ type: 'home' }` only on the home
59
+ page. If you leave out `pageType`, the helper sends `{ type: 'other' }`.
60
+
61
+ ## Register the scripts
62
+
63
+ Complete your [framework quickstart](https://c15t.com/docs/frameworks) first. Keep its Inth
64
+ endpoint, policy, styles and consent UI. Remove the vendor's original script,
65
+ SDK initializer or tag-manager entry, so the vendor loads only through c15t.
66
+
67
+ The vendor pages put the helper in `src/consent-scripts.ts`. If your framework
68
+ quickstart already has a `scripts` array, such as the one in `c15t.config.ts`
69
+ in the Next.js guide, add the helper to that array instead of creating a
70
+ second file.
71
+ The `scripts` export is a configuration, not an initializer. Add it to the c15t provider you already have, at the registration
72
+ point for your framework below. These are edits to that provider, not a second
73
+ provider.
74
+
75
+ **Next.js**
76
+
77
+ Add the configuration to `scripts` in `c15t.config.ts`, next to
78
+ `next.config.ts`:
79
+
80
+ ```ts title="c15t.config.ts"
81
+ import { defineConsentConfig } from 'c15t/next';
82
+ import { scripts } from './src/consent-scripts';
83
+
84
+ export default defineConsentConfig({ scripts });
85
+ ```
86
+
87
+ Keep the rest of your config, such as `mode` and `routePrefix`, in the
88
+ same call. `ConsentRoot` reads the config in the browser, so the layout
89
+ keeps passing only `state`. App Router, Pages Router and static export all
90
+ read the same file. See
91
+ [Next.js scripts and embeds](https://c15t.com/docs/frameworks/next/scripts).
92
+
93
+ **TanStack Start**
94
+
95
+ Import the configuration into your root route and pass it to the existing
96
+ `ConsentRoot` as a top-level prop. Keep the loader from the
97
+ [TanStack Start quickstart](https://c15t.com/docs/frameworks/tanstack-start/quickstart):
98
+
99
+ ```tsx title="src/routes/__root.tsx"
100
+ import { scripts } from '../consent-scripts';
101
+
102
+ <ConsentRoot state={consent} scripts={scripts}>
103
+ ```
104
+
105
+ Import vendor helpers in the root route module, not in a server function.
106
+ A server function's return value must be serializable, and script
107
+ configurations carry callbacks. See
108
+ [TanStack Start scripts](https://c15t.com/docs/frameworks/tanstack-start/scripts).
109
+
110
+ **React**
111
+
112
+ Add the configuration to the existing `ConsentProvider` options, next to
113
+ `mode`:
114
+
115
+ ```tsx title="src/consent.tsx"
116
+ import { scripts } from './consent-scripts';
117
+
118
+ <ConsentProvider options={{ mode, scripts }}>
119
+ ```
120
+
121
+ `mode` is the `manifest()` value from the
122
+ [React quickstart](../frameworks/react/quickstart.md). Keep the banner,
123
+ dialog and preferences link inside the provider. See
124
+ [React scripts and embeds](../frameworks/react/scripts.md).
125
+
126
+ **Nuxt**
127
+
128
+ Register the scripts under the `c15t` key in `app/app.config.ts`. Adjust the
129
+ relative import to where you created `consent-scripts.ts`:
130
+
131
+ ```ts title="app/app.config.ts"
132
+ import { scripts } from '../src/consent-scripts';
133
+
134
+ export default defineAppConfig({
135
+ c15t: { scripts },
136
+ });
137
+ ```
138
+
139
+ The Nuxt module merges this over its options in `nuxt.config.ts` and starts
140
+ one script loader in the browser after hydration, once it has applied the
141
+ visitor's stored choice and privacy signals. Keep `scripts` out of
142
+ `nuxt.config.ts`, which reaches the browser as JSON and drops the vendor
143
+ callbacks. Write the vendor IDs into `consent-scripts.ts`. See
144
+ [Nuxt scripts and embeds](https://c15t.com/docs/frameworks/nuxt/scripts).
145
+
146
+ **Vue**
147
+
148
+ Pass the scripts to the existing `c15tVue` plugin call in `src/main.ts`:
149
+
150
+ ```ts title="src/main.ts"
151
+ import { c15tVue, manifest } from 'c15t/vue/vue-plugin';
152
+ import { scripts } from './consent-scripts';
153
+
154
+ app.use(c15tVue, {
155
+ mode: manifest(),
156
+ scripts,
157
+ });
158
+ ```
159
+
160
+ Keep your existing `mode` and other options. The plugin starts one
161
+ script loader when the app mounts, after it has applied the visitor's stored
162
+ choice. Do not also call `createScriptLoader` from a component. See
163
+ [Vue scripts and embeds](https://c15t.com/docs/frameworks/vue/scripts).
164
+
165
+ **Astro**
166
+
167
+ Add the scripts to the client entrypoint from the
168
+ [Astro quickstart](https://c15t.com/docs/frameworks/astro/quickstart), `src/c15t.client.ts`,
169
+ which the integration finds on its own. Keep `astro.config.mjs` as it is.
170
+ If the module already exports scripts, combine the two arrays.
171
+
172
+ ```ts title="src/c15t.client.ts"
173
+ import type { C15tClientOptionsExtension } from 'c15t/astro';
174
+ import { scripts } from './consent-scripts';
175
+
176
+ export default { scripts } satisfies C15tClientOptionsExtension;
177
+ ```
178
+
179
+ Vendor helpers contain callbacks, and the integration options in
180
+ `astro.config.mjs` are serialized into the page, so do not put helpers in
181
+ the integration's `scripts` option. The integration passes the client
182
+ entrypoint to the one runtime every page shares, including across
183
+ `ClientRouter` navigation.
184
+
185
+ **Svelte**
186
+
187
+ Import the scripts in the component that owns your existing provider and
188
+ pass them as a top-level prop:
189
+
190
+ ```svelte title="src/App.svelte"
191
+ <script lang="ts">
192
+ import { ConsentProvider, manifest } from 'c15t/svelte';
193
+ import { scripts } from './consent-scripts';
194
+ </script>
195
+
196
+ <ConsentProvider mode={manifest()} {scripts}>
197
+ <!-- Keep your application, consent UI and preferences link here. -->
198
+ </ConsentProvider>
199
+ ```
200
+
201
+ Retain the styles and consent UI from the [Svelte quickstart](https://c15t.com/docs/frameworks/svelte/quickstart).
202
+ The provider owns the loader and disposes it on unmount.
203
+
204
+ **SvelteKit**
205
+
206
+ Add the scripts to the existing `ConsentRoot` in the root layout. Keep the
207
+ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs/frameworks/sveltekit/quickstart).
208
+
209
+ ```svelte title="src/routes/+layout.svelte"
210
+ <script lang="ts">
211
+ import { ConsentRoot } from 'c15t/svelte';
212
+ import { scripts } from '../consent-scripts';
213
+
214
+ let { children, data } = $props();
215
+ </script>
216
+
217
+ <ConsentRoot state={data.consent} {scripts}>
218
+ {@render children()}
219
+ <!-- Keep your consent UI and preferences link here. -->
220
+ </ConsentRoot>
221
+ ```
222
+
223
+ Import vendor helpers in the layout component, not in `+layout.server.ts`:
224
+ a server load cannot send functions to the browser. Prerendered, static and
225
+ SPA-mode pages use the same `scripts` prop. If you pass an externally owned
226
+ `runtime` to the provider, register scripts when creating that runtime instead.
227
+
228
+ **HTML**
229
+
230
+ The helpers in `@c15t/integrations` are ES modules that need a bundler. On a
231
+ page that loads the c15t script tag, paste the vendor's own snippet instead
232
+ and keep it inert until its category is allowed:
233
+
234
+ ```html
235
+ <script type="text/plain" data-c15t-category="measurement">
236
+ // The vendor's snippet, unchanged
237
+ </script>
238
+ ```
239
+
240
+ Use the category this guide names for the vendor. c15t runs the snippet
241
+ once that category is allowed, and reloads the page when the visitor
242
+ withdraws it. Helper options on this page, such as `loadMode`, do not apply
243
+ to a pasted snippet. See [HTML scripts](https://c15t.com/docs/frameworks/html/scripts).
244
+
245
+ **JavaScript**
246
+
247
+ Pass the scripts to `init()` from `@c15t/browser`, next to your mode:
248
+
249
+ ```ts
250
+ import { init, manifest } from '@c15t/browser';
251
+ import { scripts } from './consent-scripts';
252
+
253
+ const consent = init({
254
+ mode: manifest(),
255
+ scripts,
256
+ });
257
+ ```
258
+
259
+ Keep the mode from your quickstart. With
260
+ `createConsentRuntime` from `c15t/runtime`, pass `scripts` to it instead.
261
+ A kernel you create yourself needs a loader from
262
+ `c15t/modules/script-loader`. Attach one loader per kernel. See
263
+ [JavaScript scripts](https://c15t.com/docs/frameworks/javascript/scripts).
264
+
265
+ ## Options
266
+
267
+ | Option | Default | Behavior |
268
+ | ------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
269
+ | `partnerName` | Required | Public partner name from Insider. Trimmed, lowercased and checked as a single DNS label for `<partnerName>.api.useinsider.com`. If it's missing, blank or invalid, c15t logs `insiderTag: missing or invalid partnerName` and never loads Insider. |
270
+ | `partnerId` | Required | Public partner ID from Insider. Trimmed and URL-encoded as one query value. If it's missing or blank, c15t logs `insiderTag: missing or invalid partnerId` and never loads Insider. |
271
+ | `user` | Omitted | Complete current user attributes for the grant push, including checkout identifiers and account-specific `custom` data. The helper sets `gdpr_optin` itself and sends none of these attributes on denial. If the object can't be serialized to JSON, such as a circular reference or a `BigInt`, c15t logs `insiderTag: user must be JSON-serializable` and never loads Insider. |
272
+ | `currency` | Omitted | Current currency string, such as `'GBP'`, queued as `{ type: 'currency', value: 'GBP' }` before the page and `init`. Passed as-is, with no code list or case change. If it's set but isn't a non-blank string, c15t logs `insiderTag: currency must be a non-blank string` and never loads Insider. |
273
+ | `pageType` | `{ type: 'other' }` | Complete page push, including any required `value` fields. A purchase needs the per-order guard described below. |
274
+ | `category` | `'marketing'` | Consent condition needed before the helper creates the queue or loads the tag. Accepts a category or an `and`/`or`/`not` condition. |
275
+
276
+ Invalid options don't throw. The helper returns a script that never loads, so
277
+ your consent UI still renders and Insider stays listed in the preference
278
+ dialog. Check the console for `insiderTag:` errors if the tag never appears.
279
+
280
+ For an account classified as personalization, you can set
281
+ `category: 'experience'`. The same condition controls loading and the
282
+ `gdpr_optin` value sent on revocation.
283
+
284
+ ## Supply the current user and currency
285
+
286
+ Insider uses the last successful `user` push when `init` runs. Pass the
287
+ complete current user's attributes in `user`. An earlier queue entry isn't a
288
+ way to supply them. The helper merges these attributes into its grant push and
289
+ sets `gdpr_optin: true` after them, so your data can't override consent. It
290
+ never recovers identity from earlier queue entries. Without `user`, it sends
291
+ only `{ gdpr_optin: true }` on grant.
292
+
293
+ Use Insider's attribute names and types. For example, `email`, `phone_number`
294
+ and `uuid` are strings, `age` is a number, `email_optin` is a boolean, and
295
+ `custom` holds account-specific attributes. Keep custom identifiers inside
296
+ `custom`, set them up in Insider's Identity Resolution Management settings,
297
+ and send their values as strings. Follow the Web SDK guide for field formats,
298
+ such as E.164 phone numbers. Nested objects pass through unchanged. The helper
299
+ doesn't check whether your account supports them.
300
+
301
+ Insider documents that logging out doesn't reset the browser's Insider ID. For
302
+ a guest purchase, pass the current guest's checkout identifiers, such as email
303
+ and phone number, in `user` before the purchase `init`. Don't reuse the
304
+ previous customer's identifiers or make up an ID from an order. Insider's
305
+ matching and merging still depend on those identifiers and your account
306
+ settings.
307
+
308
+ ```ts title="src/consent-scripts.ts"
309
+ import { insiderTag } from '@c15t/integrations/insider';
310
+ import type { InsiderPageType } from '@c15t/integrations/insider';
311
+
312
+ export const createInsiderScripts = function createInsiderScripts(
313
+ currentUser: Record<string, unknown>,
314
+ currency: string,
315
+ guardedPage: InsiderPageType
316
+ ) {
317
+ return [
318
+ insiderTag({
319
+ currency,
320
+ pageType: guardedPage,
321
+ partnerId: 'YOUR_PARTNER_ID',
322
+ partnerName: 'yourpartner',
323
+ user: currentUser,
324
+ }),
325
+ ];
326
+ };
327
+ ```
328
+
329
+ Build this configuration from the current checkout or session data, storefront
330
+ currency and guarded page definition, then register it with your consent
331
+ adapter. Keep the returned scripts stable while those values are current.
332
+
333
+ The helper copies `user` when you call it, so later changes to that object,
334
+ including nested data, don't reach Insider. It doesn't watch login, logout,
335
+ checkout, currency or route changes. Rebuild the configuration through your
336
+ session and order logic before a new registration or page load. Recreating a
337
+ helper that's already active doesn't send new user data or restart it.
338
+
339
+ Multi-currency sites must pass the currency used for the displayed prices. The
340
+ helper queues `user`, then `currency`, then the page, then `init`. Insider keeps
341
+ user data and currency between initializations, so if you leave out
342
+ `currency`, an earlier or account-default value can stay active. On a
343
+ single-currency site, confirm with Insider that you can leave it out. The
344
+ helper doesn't convert prices or clear Insider's stored identity or currency.
345
+
346
+ ## Define the page context
347
+
348
+ `pageType` is an object with the SDK's `type` and `value` fields. Insider
349
+ documents these six page definitions. Basket pages are `cart`, not `basket`,
350
+ and order confirmation pages are `purchase`, not `confirmation`.
351
+
352
+ | `type` | `value` |
353
+ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
354
+ | `'home'` | Optional `{ custom }` event parameters. |
355
+ | `'other'` | Optional `{ name, custom }` for any other page. |
356
+ | `'category'` | `{ breadcrumb: string[] }` for a listing page. Insider needs the category hierarchy for many use cases. |
357
+ | `'product'` | Product data with `id`, `name`, `taxonomy`, `unit_price`, `unit_sale_price`, `url` and `product_image_url`. |
358
+ | `'cart'` | `{ total, items, shipping_cost? }`. Each item needs the product fields and `quantity`. |
359
+ | `'purchase'` | `{ order_id, total, items, shipping_cost? }`. Each item needs the product fields and `quantity`. Only for a newly completed order, after the per-order guard allows it. |
360
+
361
+ Products also accept the optional string fields `brand`, `description`, `gtin`,
362
+ `sku`, `identifier_exists`, `locale`, `google_title`, `gender` and
363
+ `omnibus_price`, plus `groupcode`, `color`, `size`, numeric `stock` and
364
+ account-specific `custom` data. Cart and purchase items accept these too.
365
+ Insider's Web SDK guide types `identifier_exists` and `omnibus_price` as
366
+ strings.
367
+
368
+ ```ts title="src/consent-scripts.ts"
369
+ import { insiderTag } from '@c15t/integrations/insider';
370
+
371
+ export const scripts = [
372
+ insiderTag({
373
+ currency: 'GBP',
374
+ pageType: {
375
+ type: 'product',
376
+ value: {
377
+ brand: 'Example Clothing',
378
+ description: 'Blue cotton dress',
379
+ gender: 'female',
380
+ google_title: 'Blue cotton dress',
381
+ gtin: '00012345678905',
382
+ id: 'dress-blue',
383
+ identifier_exists: 'true',
384
+ locale: 'en_US',
385
+ name: 'Blue dress',
386
+ omnibus_price: '90.00',
387
+ product_image_url: 'https://example.com/blue-dress.png',
388
+ sku: 'DRESS-BLUE-M',
389
+ taxonomy: ['Clothing', 'Dresses'],
390
+ unit_price: 100,
391
+ unit_sale_price: 90,
392
+ url: 'https://example.com/blue-dress',
393
+ },
394
+ },
395
+ partnerId: 'YOUR_PARTNER_ID',
396
+ partnerName: 'yourpartner',
397
+ }),
398
+ ];
399
+ ```
400
+
401
+ Page values must be JSON-serializable. The helper passes custom data through as
402
+ literal JSON, including objects shaped like c15t runtime values
403
+ (`kind: 'c15t.runtime-value'` with `value: 'date'` or `'timestamp'`). It
404
+ doesn't treat them as instructions. Normal JSON rules apply: a `Date` becomes a
405
+ string, and properties set to `undefined` are dropped. Queue consumers get a
406
+ copy, so their changes don't alter your page data. Server-started scripts use
407
+ the same conversion.
408
+
409
+ Insider documents that every `init` updates the profile and triggers a
410
+ page-view event for the last defined page.
411
+
412
+ The helper sets the first page only. It doesn't watch client-side route
413
+ changes. For single-page navigation, follow Insider's
414
+ [Web SDK guide](https://academy.insiderone.com/docs/insider-web-sdk-integration-guide):
415
+ push the new page definition and then `init`, but only while the consent
416
+ condition and the Insider vendor switch allow it. When the user or currency
417
+ changes, push the complete current user and currency before the page and
418
+ `init`. Your app owns these updates and any stale identity, currency or cart
419
+ data. Set `gdpr_optin` in every `user` you push yourself, because Insider
420
+ treats a missing value as `true`.
421
+
422
+ ### Prevent duplicate purchases
423
+
424
+ **A purchase configuration needs a durable per-order guard in your app.** The
425
+ helper doesn't deduplicate `order_id`. If you configure `purchase` whenever the
426
+ confirmation URL renders, every allowed load queues the order again. Insider's
427
+ [confirmation-page guide](https://academy.insiderone.com/docs/integrate-insider-into-your-confirmation-page)
428
+ forbids repeating the purchase after a refresh, Back navigation, another page
429
+ visit, or opening the confirmation URL in a new tab.
430
+
431
+ Claim the order atomically in your order backend, keyed by the completed order
432
+ ID, before you send a purchase payload to the page. Only the request that
433
+ claims the order may build `pageType: { type: 'purchase', value: ... }`. Return
434
+ `{ type: 'other' }` for later requests. An in-memory flag or a URL check can't
435
+ protect against a reload or a second tab. Only confirmed, successful orders
436
+ qualify, and the confirmation page must hold no payment card data.
437
+
438
+ Don't reuse a claimed purchase configuration when you recreate the helper or
439
+ after a withdrawal and regrant. Keep the default reload on revocation, and
440
+ rebuild the page configuration through the same guard. Apply the guard to
441
+ purchases you push yourself during SPA navigation too. If your app can't
442
+ enforce this, use `other` instead of `purchase`.
443
+
444
+ The guard limits purchase attempts but doesn't guarantee delivery. A claimed
445
+ order can miss Insider if the visitor denies consent or the loader fails. The
446
+ guard itself must not create Insider globals or send Insider requests before
447
+ consent. The `user` and `currency` options don't deduplicate purchases.
448
+
449
+ ## Loading and revocation
450
+
451
+ The loader URL is
452
+ `https://<partnerName>.api.useinsider.com/ins.js?id=<partnerId>`. After
453
+ consent, c15t adds it to `<head>` as an async script. Before the first grant,
454
+ the helper creates no Insider global, request, cookie or Web Storage entry.
455
+ That holds only if you removed other Insider installations.
456
+
457
+ If the visitor withdraws the category or turns off the Insider vendor switch,
458
+ the helper pushes `{ type: 'user', value: { gdpr_optin: false } }`, then
459
+ `{ type: 'other' }`, then `{ type: 'init' }` through `InsiderQueue.push`. You
460
+ don't need to call the queue yourself. The neutral page replaces the SDK's last
461
+ page before that `init`, so it doesn't replay a purchase. This happens on every
462
+ withdrawal, including after your app pushed a purchase during SPA navigation.
463
+
464
+ The denial push contains only `gdpr_optin: false`. The helper doesn't resend
465
+ your `user` attributes or currency on withdrawal, even if `user` contains
466
+ `gdpr_optin: true`, so the denial push adds no checkout or identity data. An
467
+ SDK that is still loading can process the earlier grant before the denial. It
468
+ doesn't clear identity or currency Insider already holds.
469
+
470
+ This is a consent update through the same `user` and `init` steps used on
471
+ grant. It doesn't unload the SDK or stop every request. Insider says
472
+ `gdpr_optin: false` blocks campaigns, except cookieless ones, and blocks
473
+ Insider messages. The final `init` can trigger an Other Page View, so the
474
+ running page can still send traffic after withdrawal. Replacing the page
475
+ doesn't remove purchase entries queued earlier, so you still need the per-order
476
+ guard and a guarded rebuild on reload or regrant.
477
+
478
+ c15t removes the loader, but a download in flight can still finish, and code
479
+ that already ran can keep listeners or timers. If consent is withdrawn before
480
+ the SDK starts, the queue still holds the grant and `init` ahead of the denial.
481
+ A late SDK can process those earlier entries first. Keep
482
+ `reloadOnConsentRevoked` enabled. On the next page with consent denied, c15t
483
+ creates no queue and loads no Insider tag. The reload doesn't wait for Insider
484
+ to confirm the update and doesn't delete existing vendor storage.
485
+
486
+ ## Cookies and storage
487
+
488
+ The helper writes no cookies, localStorage or sessionStorage itself. After
489
+ consent, Insider's tag can use all three. The public loader contains cookie and
490
+ Web Storage code, with names such as `spUID`, `ins-current-currency` and
491
+ `ins-cart-product-list`. These names come from the loader source. They aren't
492
+ a list observed in a browser, and not every account writes them. Campaigns,
493
+ account settings and SDK updates can change the storage used.
494
+
495
+ Check your account in DevTools Application after a grant and again after a
496
+ withdrawal. Record cookie names, domains, paths and expiries, plus localStorage
497
+ and sessionStorage keys. Set up c15t's clear-on-revocation rules from that
498
+ list. The helper doesn't guess storage prefixes to delete, and browser
499
+ JavaScript can't remove HttpOnly or third-party cookies.
500
+
501
+ ## Verify Insider One
502
+
503
+ Use an opt-in policy in a fresh private window and filter Network by
504
+ `useinsider.com`. Before a grant, including after a rejection and reload, no
505
+ Insider request appears and `window.InsiderQueue` is undefined.
506
+
507
+ After a grant, look for the partner-specific `ins.js` request. Check that the
508
+ final user push holds the current identifiers, followed by the currency, page
509
+ and `init`. In your Insider account, check the resulting profile, page context
510
+ and price currency, including a guest checkout after a previous customer logged
511
+ out. Queue contents alone don't prove profile matching or delivery.
512
+
513
+ Test both category withdrawal and the Insider vendor switch. The next denied
514
+ page makes no Insider request. Check existing storage separately, because a
515
+ removed loader doesn't prove cleanup.
516
+
517
+ A placeholder partner can return HTTP 200 with an HTML error body. That proves
518
+ the endpoint answered, not that the SDK ran or that your account accepted your
519
+ events.
520
+
521
+ Sources: [integrate the Insider tag](https://academy.insiderone.com/docs/integrate-insider-tag-into-your-website),
522
+ [Insider Web SDK integration guide](https://academy.insiderone.com/docs/insider-web-sdk-integration-guide),
523
+ [cookie-free Insider tag behavior](https://academy.insiderone.com/docs/how-does-cookie-free-insider-tag-work),
524
+ and [Insider One privacy policy](https://insiderone.com/privacy-policy/).
525
+
526
+ Test in a private window with an opt-in policy. Open DevTools Network, disable
527
+ the cache and filter by the vendor's domain:
528
+
529
+ 1. Load the page. No request goes to the vendor before you choose.
530
+ 2. Click Reject, then reload. There is still no vendor request.
531
+ 3. Open Privacy settings and allow the helper's category. The vendor script
532
+ loads without a page reload.
533
+ 4. Turn the category off again and save. c15t reloads the page, and the new
534
+ page makes no vendor request.
535
+
536
+ c15t reloads on revocation because removing a script element does not stop
537
+ code that already ran. The vendor's listeners, timers and queued events stay
538
+ alive until the page unloads. If you set `reloadOnConsentRevoked: false`, stop
539
+ the vendor yourself. Register a callback-only script whose `onConsentChange`
540
+ calls the vendor's opt-out API, as shown in
541
+ [custom integrations](./building-integrations.md), and check the
542
+ permission before each of your own event calls. The reload does not delete
543
+ cookies the vendor already set; see
544
+ [clear on revocation for your framework](./overview.md#vendor-switches-and-cookie-cleanup).
545
+
546
+ The helper lists its vendor by name in the preference dialog, so a visitor can
547
+ turn it off inside an allowed category. See
548
+ [vendor consent for your framework](./overview.md#vendor-switches-and-cookie-cleanup). The
549
+ [consent verification guide](../guides/verify-consent.md) covers navigation,
550
+ expiry and hosting checks.
@@ -165,7 +165,7 @@ pass them as a top-level prop:
165
165
 
166
166
  ```svelte title="src/App.svelte"
167
167
  <script lang="ts">
168
- import { ConsentProvider, manifest } from '@c15t/svelte';
168
+ import { ConsentProvider, manifest } from 'c15t/svelte';
169
169
  import { scripts } from './consent-scripts';
170
170
  </script>
171
171
 
@@ -184,7 +184,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
184
184
 
185
185
  ```svelte title="src/routes/+layout.svelte"
186
186
  <script lang="ts">
187
- import { ConsentRoot } from '@c15t/svelte';
187
+ import { ConsentRoot } from 'c15t/svelte';
188
188
  import { scripts } from '../consent-scripts';
189
189
 
190
190
  let { children, data } = $props();
@@ -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();
@@ -166,7 +166,7 @@ pass them as a top-level prop:
166
166
 
167
167
  ```svelte title="src/App.svelte"
168
168
  <script lang="ts">
169
- import { ConsentProvider, manifest } from '@c15t/svelte';
169
+ import { ConsentProvider, manifest } from 'c15t/svelte';
170
170
  import { scripts } from './consent-scripts';
171
171
  </script>
172
172
 
@@ -185,7 +185,7 @@ handle and the layout load from the [SvelteKit quickstart](https://c15t.com/docs
185
185
 
186
186
  ```svelte title="src/routes/+layout.svelte"
187
187
  <script lang="ts">
188
- import { ConsentRoot } from '@c15t/svelte';
188
+ import { ConsentRoot } from 'c15t/svelte';
189
189
  import { scripts } from '../consent-scripts';
190
190
 
191
191
  let { children, data } = $props();