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

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 (60) hide show
  1. package/AGENTS.md +2 -1
  2. package/dist/component-hooks/use-headless-consent-ui.js +1 -1
  3. package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
  4. package/dist/components/preferences/atoms/accordion.js +1 -1
  5. package/dist/components/shared/primitives/button.js +1 -1
  6. package/dist/draft-context.js +2 -0
  7. package/dist/draft.js +1 -1
  8. package/dist/external-iab-context.js +1 -1
  9. package/dist/gpp.js +2 -0
  10. package/dist/hooks/use-color-scheme.js +1 -1
  11. package/dist/hooks.js +1 -1
  12. package/dist/index-parts/consent-banner.js +1 -0
  13. package/dist/index-parts/consent-dialog-link.js +1 -0
  14. package/dist/index-parts/consent-dialog-trigger.js +1 -0
  15. package/dist/index-parts/consent-dialog.js +1 -0
  16. package/dist/index-parts/consent-gate.js +1 -0
  17. package/dist/index-parts/core.js +1 -0
  18. package/dist/index-parts/draft.js +1 -0
  19. package/dist/index-parts/hooks.js +1 -0
  20. package/dist/index-parts/module-hooks.js +1 -0
  21. package/dist/index-parts/provider.js +1 -0
  22. package/dist/index-parts/theme.js +1 -0
  23. package/dist/index-parts/translations.js +1 -0
  24. package/dist/index.js +1 -1
  25. package/dist/kernel-selector.js +1 -1
  26. package/dist/module-hooks/iframe-blocker.js +1 -1
  27. package/dist/provider.js +1 -1
  28. package/dist/version.js +1 -1
  29. package/dist-types/draft-context.d.ts +31 -0
  30. package/dist-types/draft.d.ts +13 -36
  31. package/dist-types/gpp.d.ts +27 -0
  32. package/dist-types/hooks.d.ts +3 -0
  33. package/dist-types/index-parts/consent-banner.d.ts +7 -0
  34. package/dist-types/index-parts/consent-dialog-link.d.ts +7 -0
  35. package/dist-types/index-parts/consent-dialog-trigger.d.ts +7 -0
  36. package/dist-types/index-parts/consent-dialog.d.ts +7 -0
  37. package/dist-types/index-parts/consent-gate.d.ts +7 -0
  38. package/dist-types/index-parts/core.d.ts +8 -0
  39. package/dist-types/index-parts/draft.d.ts +7 -0
  40. package/dist-types/index-parts/hooks.d.ts +7 -0
  41. package/dist-types/index-parts/module-hooks.d.ts +7 -0
  42. package/dist-types/index-parts/provider.d.ts +7 -0
  43. package/dist-types/index-parts/theme.d.ts +8 -0
  44. package/dist-types/index-parts/translations.d.ts +7 -0
  45. package/dist-types/index.d.ts +14 -15
  46. package/dist-types/kernel-selector.d.ts +0 -8
  47. package/dist-types/module-hooks/iframe-blocker.d.ts +12 -7
  48. package/dist-types/provider.d.ts +36 -13
  49. package/dist-types/version.d.ts +1 -1
  50. package/docs/README.md +2 -1
  51. package/docs/concepts/consent-state.md +19 -5
  52. package/docs/customization/translations.md +1 -2
  53. package/docs/frameworks/react/components/consent-provider.md +6 -2
  54. package/docs/frameworks/react/components/dev-tools.md +1 -1
  55. package/docs/frameworks/react/gpp.md +163 -0
  56. package/docs/integrations/posthog.md +99 -11
  57. package/docs/upgrade-v3.md +22 -1
  58. package/package.json +12 -7
  59. package/dist/ui-save.js +0 -1
  60. package/dist-types/ui-save.d.ts +0 -25
@@ -32,31 +32,30 @@
32
32
  * module wiring around a single per-mount kernel.
33
33
  */
34
34
  export type { ConsentKernel, ConsentSnapshot, ConsentState, HostedTransportOptions, InitContext, InitResponse, InitResult, KernelConfig, KernelEvent, KernelOverrides, KernelTransport, KernelUser, Listener, HostedModeOptions, ProviderTransportContext, ProviderTransportFactory, ProviderTransportKind, SavePayload, SaveResult, Unsubscribe, } from '@c15t/core';
35
- export { createConsentKernel, custom, hosted } from '@c15t/core';
36
35
  export type { OfflineModeOptions } from './transports/offline.js';
37
- export { offline } from './transports/offline.js';
38
- export { ConsentDialog, ConsentWidget } from './aggregate-components.js';
39
36
  export type { ConsentBannerButton, ConsentBannerLayout, ConsentBannerProps, ConsentBannerRight, ConsentBannerRightLinkProps, ConsentBannerRightsProps, ConsentBannerSurface, } from './components/prompt/index.js';
40
- export { ConsentBanner, type ConsentBannerCompoundComponent, useConsentBannerSurface, } from './components/prompt/index.js';
41
37
  export type { ConsentDialogCompoundComponent, ConsentDialogProps, } from './components/panel/index.js';
42
38
  export type { ConsentDialogLinkProps } from './components/panel-link/index.js';
43
- export { ConsentDialogLink } from './components/panel-link/index.js';
44
39
  export type { ConsentDialogTriggerProps, ConsentDialogTriggerToolbarAction, ConsentDialogTriggerToolbarPreferences, ConsentDialogTriggerToolbarProps, TriggerOrientation, } from './components/panel-trigger/index.js';
45
- export { ConsentDialogTrigger, ConsentDialogTriggerToolbar, } from './components/panel-trigger/index.js';
46
40
  export type { ConsentWidgetCompoundComponent, ConsentWidgetProps, } from './components/preferences/index.js';
47
41
  export type { ConsentGateProps, FrameProps } from './components/consent-gate/index.js';
48
- export { ConsentGate, Frame } from './components/consent-gate/index.js';
49
42
  export type { ConsentDraftHandle, ConsentDraftProviderProps, VendorDraftHandle, } from './draft.js';
50
- export { ConsentDraftProvider, useConsentDraft, useVendorDraft } from './draft.js';
51
- export { useTranslations } from './component-hooks/use-translations.js';
52
- export { useActiveUI, useBranding, useConsent, useConsents, useExperiment, useHasConsentPolicy, useHasConsentPreferences, useHasConsentUI, useIABEnabled, useIABSnapshot, useIdentify, useInit, useLocation, useModel, useOverrides, usePromptPresentation, usePolicyCategories, usePreferencesPresentation, useResolvedPresentation, useResolvedTheme, usePolicyScopeMode, usePurposeConsent, useRegisterConsentCategories, useSaveConsents, useSetActiveUI, useSetLanguage, useSetOverrides, useSnapshot, useSpecialFeatureOptIn, useSubscribeToConsentChanges, useTCString, useUser, useDeclaredVendors, useVendorAllowed, useVendorChoice, useVendorConsent, } from './hooks.js';
53
43
  export type { UseIframeBlockerOptions, UseNetworkBlockerOptions, UsePersistenceOptions, UseScriptLoaderOptions, } from './module-hooks.js';
54
- export { useIframeBlocker, useNetworkBlocker, usePersistence, useScriptLoader, } from './module-hooks.js';
55
44
  export type { ConsentProviderOptions, ConsentProviderCallbacks, ConsentProviderPrefetch, ConsentProviderProps, ExternalRuntimeProviderOptions, ExternalRuntimeProviderProps, OwnedRuntimeProviderProps, } from './provider.js';
56
- export { ConsentProvider } from './provider.js';
57
- export { ConsentTheme } from './consent-theme.js';
58
45
  export type { ConsentThemeProps } from './consent-theme.js';
46
+ export type { Theme } from './types/theme/index.js';
47
+ export type { ConsentBannerCompoundComponent } from './components/prompt/index.js';
59
48
  export type { ReactUIOptions } from './types/manager.js';
60
- export { defineTheme, type Theme } from './types/theme/index.js';
61
- export { useExplicitChoice, useEffectivePermissions, usePromptRequirement, useNoticeDismissal, usePrivacySignals, usePolicyResolution, usePolicyRule, useRestrictions, useDismissNotice, } from './hooks.js';
62
49
  export type { ConsentPresentation, PromptPresentation, PreferencesPresentation, PresentationAction, PromptPosition, PromptVariant, ResolvedConsentPresentation, } from '@c15t/core';
50
+ export * from './index-parts/core.js';
51
+ export * from './index-parts/consent-dialog.js';
52
+ export * from './index-parts/consent-banner.js';
53
+ export * from './index-parts/consent-dialog-link.js';
54
+ export * from './index-parts/consent-dialog-trigger.js';
55
+ export * from './index-parts/consent-gate.js';
56
+ export * from './index-parts/draft.js';
57
+ export * from './index-parts/translations.js';
58
+ export * from './index-parts/module-hooks.js';
59
+ export * from './index-parts/provider.js';
60
+ export * from './index-parts/theme.js';
61
+ export * from './index-parts/hooks.js';
@@ -56,11 +56,3 @@ export declare const useCategoryAllowed: (category: AllConsentNames) => boolean;
56
56
  * @internal
57
57
  */
58
58
  export declare const useTranslationLanguage: () => string;
59
- /**
60
- * Categories the preference surface lists: `necessary` plus the recorded
61
- * choice scope, or the policy scope when no choice scope applies.
62
- *
63
- * @returns A list whose identity changes only when the scope does.
64
- * @internal
65
- */
66
- export declare const useDisplayedCategories: () => readonly AllConsentNames[];
@@ -1,5 +1,5 @@
1
1
  import type { ConsentKernel } from '@c15t/core';
2
- import type { IframeBlockerHandle } from '@c15t/core/modules/iframe-blocker';
2
+ import type { IframeBlockerHandle, IframeBlockerOptions } from '@c15t/core/modules/iframe-blocker';
3
3
  export interface UseIframeBlockerOptions {
4
4
  disableAutomaticBlocking?: boolean;
5
5
  }
@@ -21,13 +21,18 @@ export declare const useIframeBlocker: (options?: UseIframeBlockerOptions) => If
21
21
  */
22
22
  export declare const watchGatedIframes: (kernel: ConsentKernel, onFound: () => void) => () => void;
23
23
  /**
24
- * The provider's default iframe blocker. Loads the blocker module when the
25
- * first gated iframe (`data-category` or `data-vendor`) is on the page
26
- * instead of on every mount, so pages without one never download it.
24
+ * The provider's iframe blocker. Loads the blocker module when the first
25
+ * gated iframe (`data-category` or `data-vendor`) is on the page instead of
26
+ * on every mount, so pages without one never download it. Until then it
27
+ * pauses gated frames that arrive with a `src` consent does not allow.
27
28
  *
28
- * @param options - Blocker options. With `disableAutomaticBlocking` the
29
- * module loads at once, as it did before.
29
+ * Passed to the provider runtime as its `createIframeBlocker` module.
30
+ *
31
+ * @param options - The kernel and blocker options. With
32
+ * `disableAutomaticBlocking` the module loads at once.
33
+ * @returns A handle; `processAllIframes()` reaches the blocker once it has
34
+ * loaded.
30
35
  * @internal
31
36
  */
32
- export declare const useIframeBlockerOnDemand: (options?: UseIframeBlockerOptions) => void;
37
+ export declare const createIframeBlockerOnDemand: ({ disableAutomaticBlocking, kernel, }: IframeBlockerOptions) => IframeBlockerHandle;
33
38
  export type { IframeBlockerHandle };
@@ -1,6 +1,7 @@
1
- import type { AllConsentNames, ClearOnRevocationConfig, ConsentExperiment, ExperimentState, ConsentPresentation, Callbacks, I18nConfig, KernelConfig, KernelOverrides, KernelUser, LegalLinks, ProviderTransportFactory, StorageConfig, User, Vendor } from '@c15t/core';
1
+ import type { AllConsentNames, Callbacks, ClearOnRevocationConfig, ConsentExperiment, ConsentPresentation, ExperimentState, I18nConfig, KernelConfig, KernelOverrides, KernelUser, LegalLinks, ProviderTransportFactory, StorageConfig, User, Vendor } from '@c15t/core';
2
2
  import type { Script } from '@c15t/core/modules/script-loader';
3
- import type { ConsentControlOptions, ConsentRuntime } from '@c15t/core/runtime';
3
+ import type { ConsentControlOptions } from '@c15t/core/runtime';
4
+ import type { ConsentRuntime, ResolveStreamedInit } from '@c15t/core/runtime/provider';
4
5
  import type { ReactNode } from 'react';
5
6
  import type { DialogPreload } from './chunk-warming.js';
6
7
  import type { UseNetworkBlockerOptions, UsePersistenceOptions, UseScriptLoaderOptions } from './module-hooks.js';
@@ -56,6 +57,12 @@ export interface ConsentProviderOptions extends ConsentControlOptions, Pick<Reac
56
57
  * ```
57
58
  */
58
59
  mode: ProviderTransportFactory;
60
+ /**
61
+ * Where the visitor's choice is stored. Read once, like `persistence`:
62
+ * the stored records and the data clearing that protects them stay at
63
+ * the location the provider mounted with. Outside production, a change
64
+ * logs a warning; remount the provider to move storage.
65
+ */
59
66
  storageConfig?: StorageConfig;
60
67
  user?: User | KernelUser;
61
68
  overrides?: KernelOverrides;
@@ -123,6 +130,10 @@ export interface ConsentProviderOptions extends ConsentControlOptions, Pick<Reac
123
130
  * is paused.
124
131
  */
125
132
  iframeBlocker?: UseIframeBlockerOptions | false;
133
+ /**
134
+ * Store the visitor's choice in a cookie and localStorage. On by default.
135
+ * Read once, when the provider mounts; see `storageConfig`.
136
+ */
126
137
  persistence?: boolean | UsePersistenceOptions;
127
138
  i18n?: Partial<I18nConfig>;
128
139
  /** Categories to offer alongside discovered integration categories, within policy scope. */
@@ -150,6 +161,16 @@ export interface ConsentProviderOptions extends ConsentControlOptions, Pick<Reac
150
161
  * @internal
151
162
  */
152
163
  __debugPkg?: string;
164
+ /**
165
+ * The code that applies a `prefetch` promise, from
166
+ * `@c15t/core/runtime/streamed-init`. Without it the provider loads that
167
+ * code once `prefetch` is a promise, so an app that never streams one
168
+ * doesn't download it. A framework root whose state is usually streamed
169
+ * (`ConsentRoot`) passes it, so the state applies as soon as it arrives
170
+ * instead of after one more request. Read once, at mount.
171
+ * @internal
172
+ */
173
+ __resolveStreamedInit?: ResolveStreamedInit;
153
174
  }
154
175
  /**
155
176
  * Options accepted when an external runtime supplies the kernel.
@@ -187,19 +208,21 @@ export type ConsentProviderProps = OwnedRuntimeProviderProps | ExternalRuntimePr
187
208
  /**
188
209
  * v3 ConsentProvider.
189
210
  *
190
- * Retains the enabled kernel while disabled mode uses a separate permissive
191
- * kernel, so toggling enabled preserves recorded choices. Provides the active
192
- * kernel via context and wires the curated v2-like options surface to v3
193
- * modules. It does not mirror the
194
- * snapshot into React state; selector hooks still subscribe directly to
195
- * the kernel through `useSyncExternalStore`.
211
+ * Builds one consent runtime (`createConsentProviderRuntime`) per mounted
212
+ * provider and renders its kernel: the kernel goes into context, and
213
+ * selector hooks subscribe to it through `useSyncExternalStore`. New
214
+ * options reach the runtime through `update()`, which applies only what
215
+ * changed; `mode`, `i18n`, `experiment`, `prefetch`, `persistence`,
216
+ * `storageConfig` and `clearOnRevocation` are read once. While `enabled` is
217
+ * `false` the runtime renders a separate permissive kernel, so toggling it
218
+ * keeps the visitor's records.
196
219
  *
197
220
  * Pass `runtime` to render a runtime someone else created. The provider
198
- * then borrows its kernel and mounts none of the side-effecting modules —
199
- * no second `init()`, no second persistence handle, no second `window.c15t`
200
- * — and does not dispose it on unmount. Handing it a different runtime
201
- * switches the tree to that runtime's kernel; switching between a borrowed
202
- * runtime and a provider-built kernel still needs a remount.
221
+ * then borrows its kernel and starts nothing — no second `init()`, no
222
+ * second persistence handle, no second `window.c15t` — and does not
223
+ * dispose it on unmount. Handing it a different runtime switches the tree
224
+ * to that runtime's kernel; switching between a borrowed runtime and a
225
+ * provider-built one still needs a remount.
203
226
  *
204
227
  * @example
205
228
  * ```tsx
@@ -1 +1 @@
1
- export declare const version = "3.0.0-alpha.4";
1
+ export declare const version = "3.0.0-alpha.5";
package/docs/README.md CHANGED
@@ -38,6 +38,7 @@ These docs describe c15t v3. Find your app's row in Choose your setup, then foll
38
38
  - [Content Security Policy](./frameworks/react/content-security-policy.md): Run c15t in a React app under a Content Security Policy, allow the consent backend and vendor hosts, and pass a nonce to ConsentProvider and ConsentTheme.
39
39
  - [Customize](./frameworks/react/customize.md): Change the colors, fonts, layout, button styles and copy of the c15t banner and dialog in a React app with the stylesheet, theme tokens, provider options and slots.
40
40
  - [Embeds](./frameworks/react/embeds.md): Keep YouTube videos, maps and other iframes out of a React page until their consent category is allowed, with ConsentGate or the iframe blocker in ConsentProvider.
41
+ - [IAB GPP](./frameworks/react/gpp.md): Add the IAB Global Privacy Platform API (__gpp) to a React app with ConsentGPP from @c15t/react/gpp, so ad tech can read US state opt-outs and the TCF EU consent string.
41
42
  - [Headless](./frameworks/react/headless.md): Build a custom consent banner in React with the c15t/react/headless hooks inside your ConsentProvider.
42
43
  - [Hooks](./frameworks/react/hooks.md): Gate features and save consent choices in React components with the focused hooks exported from c15t/react.
43
44
  - [IAB TCF](./frameworks/react/iab.md): Mount the IAB TCF banner and dialog inside a React ConsentProvider and configure the CMP ID, policy and vendor data.
@@ -111,7 +112,7 @@ These docs describe c15t v3. Find your app's row in Choose your setup, then foll
111
112
  - [Pinterest Tag](./integrations/pinterest-tag.md): Load the Pinterest Tag only after marketing consent with the c15t pinterestTag helper, guard pintrk event calls, and check it in DevTools.
112
113
  - [Pirsch](./integrations/pirsch.md): Load Pirsch Analytics only after measurement consent with the c15t pirsch helper, keep custom event bindings working, and check it in DevTools.
113
114
  - [Plausible Analytics](./integrations/plausible-analytics.md): Load the Plausible Analytics tracker only after measurement consent with the c15t plausibleAnalytics helper, and check it in DevTools.
114
- - [PostHog](./integrations/posthog.md): Load PostHog before or after measurement consent with the c15t posthog helper, choose its cookieless behavior, and sync consent with an SDK you already initialize.
115
+ - [PostHog](./integrations/posthog.md): Load PostHog before or after measurement consent with the c15t posthog helper, choose its cookieless behavior, turn off PostHog modules you do not use, and sync consent with an SDK you already initialize.
115
116
  - [Promptwatch](./integrations/promptwatch.md): Load the Promptwatch attribution client only after measurement consent with the c15t promptwatch helper, and check it in DevTools.
116
117
  - [Reddit Pixel](./integrations/reddit-pixel.md): Load the Reddit Pixel only after marketing consent with the c15t redditPixel helper, guard rdt conversion calls, and check it in DevTools.
117
118
  - [RudderStack](./integrations/rudderstack.md): Load the RudderStack JavaScript SDK after measurement consent with the c15t rudderstack helper, or map c15t categories to destination consent IDs, and check each mode.
@@ -31,8 +31,9 @@ feature, stay blocked.
31
31
 
32
32
  A save records the choice in the browser first and sends it to the backend
33
33
  afterwards. The stock banner, preference dialog and IAB surfaces in every
34
- framework adapter, and the browser client's `acceptAll()`, `rejectAll()`
35
- and `save()`, close without waiting for the backend. In order:
34
+ framework adapter, and the `acceptAll()`, `rejectAll()` and `save()` methods
35
+ of the browser and Astro clients, close without waiting for the backend. In
36
+ order:
36
37
 
37
38
  1. In the click task, the explicit choice and effective permissions change,
38
39
  `onChoiceRecorded` and `onPermissionsChanged` run, gated scripts, iframes
@@ -52,9 +53,10 @@ and `save()`, close without waiting for the backend. In order:
52
53
 
53
54
  A failed request does not reopen the surface or roll the choice back. The
54
55
  kernel emits `command:error`, which reaches the `onError` callback in adapters
55
- that accept one, and queues the payload in localStorage. The queue is replayed
56
- after the next successful initialization and when the browser comes back
57
- online, up to 10 attempts over 7 days. A replay carries the original action
56
+ that accept one, and queues the payload in localStorage. Where localStorage is
57
+ unavailable, the queue lives in memory until the page unloads. The queue is
58
+ replayed after the next successful initialization and when the browser comes
59
+ back online, up to 10 attempts over 7 days. A replay carries the original action
58
60
  time and policy snapshot token, so the backend records when the visitor
59
61
  decided, and a duplicate submission resolves to the same consent record. A
60
62
  backend that signs policy snapshot tokens rejects a replay made after the
@@ -66,6 +68,14 @@ after its TC string is encoded, which can wait for the TCF library to load.
66
68
  If that local step records nothing, for example because the vendor list
67
69
  failed to load, the surface comes back so the visitor can try again.
68
70
 
71
+ Every adapter leaves the same surface after a save: the banner while the
72
+ policy still owes a choice or a notice, and nothing once no prompt is owed,
73
+ while the policy is still loading, or after it failed to resolve. A banner
74
+ the visitor reopened closes too. A save that records nothing new, such as an
75
+ unchanged selection, closes the surface when it resolves successfully. Opening
76
+ or closing a surface, or starting another save, before then leaves the surface
77
+ as the visitor set it.
78
+
69
79
  ## Preserve records during hydration
70
80
 
71
81
  Server helpers return records with policy information and evaluation time.
@@ -194,6 +204,10 @@ receive a stale TC string: c15t withdraws it, or holds it back, before
194
204
 
195
205
  ### Clearing records across tabs
196
206
 
207
+ `clearRecords()` also drops every save queued for replay, with or without
208
+ browser persistence, so nothing the visitor decided before the clear reaches
209
+ the backend afterwards.
210
+
197
211
  `clearRecords()` removes every record and then stores the time of the clear,
198
212
  the clear epoch, under its own key: `c15t-epoch` in localStorage and a cookie
199
213
  of the same name (`<storageKey>-epoch` with a custom `storageKey`). Clearing
@@ -68,8 +68,7 @@ The `ConsentGate` placeholder reads the `consentGate` section:
68
68
  | `consentGate.policyBlocked` | React and Vue show it in place of the title, with no button, when a strict policy leaves the category out of scope. |
69
69
 
70
70
  Earlier versions called this section `frame`. Copy under `frame` in
71
- `i18n.messages`, custom translations or an older backend's `/init` response
72
- still applies. c15t reads it as `consentGate`, a key set under `consentGate`
71
+ `i18n.messages` or custom translations still applies. c15t reads it as `consentGate`, a key set under `consentGate`
73
72
  wins over the same key under `frame`, and c15t logs a warning once outside
74
73
  production. Rename the section to `consentGate` to remove the warning.
75
74
 
@@ -49,8 +49,12 @@ this provider and passes it server-resolved state.
49
49
  ## Provider options
50
50
 
51
51
  Keep the provider mounted across client navigation. It reads `mode`,
52
- `prefetch`, `persistence`, `clearOnRevocation` and the module options once,
53
- when it mounts. Remount the provider to change them.
52
+ `prefetch`, `persistence`, `storageConfig`, `clearOnRevocation`, `i18n` and
53
+ `experiment` once, when it mounts. Outside production, changing `mode`,
54
+ `i18n`, `experiment`, `persistence` or `storageConfig` logs a warning. Remount
55
+ the provider to change them. `scripts`, `vendors`, `networkBlocker`,
56
+ `iframeBlocker`, `consentCategories`, `overrides`, `user`, `callbacks` and
57
+ `enabled` apply when they change.
54
58
 
55
59
  | Option | Purpose |
56
60
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
@@ -95,7 +95,7 @@ explicit scope or policy categories, bulk saves cover all known categories.
95
95
  | ------------ | -------------------------------------------------------------------------------------------- |
96
96
  | **Consents** | Inspect and save categories, accept all, or reject optional categories |
97
97
  | **Scripts** | Configured scripts, loading status, search, and external page resources |
98
- | **Location** | Detected jurisdiction, country, region, and consent model |
98
+ | **Location** | Resolved country and region, and location overrides |
99
99
  | **Policy** | Resolved policy and UI configuration |
100
100
  | **IAB** | Edit vendors, purposes, legitimate interests, and special features; save and copy TC strings |
101
101
  | **Events** | Timeline of consent changes, kernel events, and script lifecycle events |
@@ -0,0 +1,163 @@
1
+ ---
2
+ title: IAB GPP
3
+ description: Add the IAB Global Privacy Platform API (__gpp) to a React app with
4
+ ConsentGPP from @c15t/react/gpp, so ad tech can read US state opt-outs and the
5
+ TCF EU consent string.
6
+ group: frameworks
7
+ ---
8
+
9
+ ## What GPP does
10
+
11
+ `ConsentGPP` from `@c15t/react/gpp` installs `window.__gpp`, the GPP 1.1 CMP
12
+ API, and keeps its GPP string in step with the visitor's choices. Prebid.js,
13
+ Google Ad Manager and other ad tech read US privacy signals from it.
14
+
15
+ ## Turn on GPP
16
+
17
+ Render `ConsentGPP` inside `ConsentProvider`:
18
+
19
+ ```tsx title="src/consent.tsx"
20
+ import { ConsentGPP } from '@c15t/react/gpp';
21
+
22
+ <ConsentProvider options={{ mode, scripts }}>
23
+ <ConsentGPP />
24
+ {children}
25
+ </ConsentProvider>;
26
+ ```
27
+
28
+ `ConsentGPP` renders nothing. It installs `__gpp` after it mounts and
29
+ removes it when it unmounts. It lives in its own entry, like
30
+ `@c15t/react/iab`, so apps that do not import it bundle none of the GPP
31
+ code. `@c15t/react` already depends on `@c15t/iab`; with the `c15t`
32
+ package, import from `c15t/react/gpp`.
33
+
34
+ The props are the GPP options listed below, for example
35
+ `<ConsentGPP usFallback="none" />`. It works the same with a runtime you
36
+ pass to `ConsentProvider` as `runtime`.
37
+
38
+ The GPP string follows the policy and location your backend resolves for the
39
+ visitor, so a US visitor's state comes from the backend. With `offline()`
40
+ mode, pass the state yourself, for example
41
+ `overrides: { country: 'US', region: 'CA' }`.
42
+
43
+ To add the TCF EU section for visitors under an `iab` policy, set up
44
+ [IAB TCF](./iab.md) as well.
45
+
46
+ ## How the policy decides the section
47
+
48
+ The policy rule c15t matched for the visitor decides whether a section
49
+ applies. The visitor's location only picks which US section carries it.
50
+
51
+ | Matched rule and visitor | Section in the GPP string |
52
+ | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
53
+ | `iab` rule, with IAB TCF set up | `tcfeuv2` (ID 2): the TC String the CMP confirmed, unchanged |
54
+ | `iab` rule, any visitor, with `tcf: false` | None; `applicableSections` is `[-1]` |
55
+ | Any other rule with the `preferences` or `opt-out` right, US visitor in a state with a section | That state's section, such as `usca` (ID 8) |
56
+ | The same rule, US visitor whose region is unknown or whose state has no section | `usnat` (ID 7), MSPA US National version 2; none with `usFallback: 'none'` |
57
+ | The same rule, `usApproach: 'national'` | `usnat` for every US visitor |
58
+ | A rule without those rights, such as `none` | None |
59
+ | A visitor outside the US under any rule other than `iab` | None |
60
+
61
+ Every `opt-in` and `opt-out` rule has the `preferences` right, and every
62
+ `opt-out` rule also has `opt-out`. A `none` rule has them only if you add
63
+ them. A rule that gives the visitor no way to opt out produces no US
64
+ section, even when the visitor sends a GPC signal.
65
+
66
+ State sections exist for California, Colorado, Connecticut, Delaware, Florida,
67
+ Iowa, Minnesota, Montana, Nebraska, New Hampshire, New Jersey, Oregon,
68
+ Tennessee, Texas, Utah and Virginia. Indiana, Kentucky, Maryland and Rhode
69
+ Island are not encoded yet: their published specifications start the section
70
+ with a header that the IAB reference implementation does not encode. Visitors
71
+ there get the fallback, `usnat` by default. So does a US visitor whose region
72
+ the geo headers did not supply.
73
+
74
+ The MSPA US National specification describes `usnat` for MSPA signatories
75
+ that chose the national approach. Without `mspaMode`, c15t reports the
76
+ fallback as a transaction the MSPA does not cover (`MspaCoveredTransaction:
77
+ 2`), which still carries the opt-outs to vendors such as Prebid.js. If you
78
+ reserve `usnat` for the national approach, set `usFallback: 'none'`; those
79
+ visitors then get no section until their state resolves.
80
+
81
+ ## What the US sections report
82
+
83
+ * `SaleOptOut`, `SharingOptOut` and `TargetedAdvertisingOptOut`: `1` (opted
84
+ out) when the `marketing` category is not permitted, otherwise `2`. A
85
+ refusal, a GPC signal the rule maps to `marketing` through
86
+ `privacySignals.gpc`, and an opt-in rule without a grant all count.
87
+ * The opt-out notices: `1` (given), since the rule offers an opt-out.
88
+ * The sharing or processing notice: `1` when the rule has the `disclosure`
89
+ right, otherwise `2`.
90
+ * `Gpc`: the browser's Global Privacy Control signal, in sections that have a
91
+ GPC subsection. It is reported even when the rule does not map GPC to a
92
+ category, so give US rules a `privacySignals.gpc` mapping if the opt-outs
93
+ should follow it too.
94
+ * Sensitive data and known-child consents: `0` (not applicable). c15t has no
95
+ category for them, so the string states that you do not process sensitive
96
+ data or knowingly process children's data. If you do, GPP from c15t does
97
+ not describe your processing; collect and signal that consent separately.
98
+ * `MspaCoveredTransaction`: `2` (not covered) unless you set `mspaMode`.
99
+
100
+ The notice fields are your attestation, made through the rule's rights:
101
+ c15t cannot see whether your privacy notice is on the page. Show the notices
102
+ the rule promises, including a persistent opt-out control.
103
+
104
+ ## GPP options
105
+
106
+ Every field is optional. Pass them in the `gpp` option, where `gpp: true`
107
+ uses the defaults, as props on `ConsentGPP` in React, or to `mountGPP()` in
108
+ `@c15t/browser`.
109
+
110
+ | Option | Default | Effect |
111
+ | ------------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
112
+ | `cmpId` | the IAB CMP ID c15t holds, else `1` | CMP ID reported by `ping`. The GPP specification has string creators without a registered ID, including MSPA US National creators, use `1`. A TCF EU section needs the registered ID its TC String names. |
113
+ | `usApproach` | `'state'` | `'state'` uses the visitor's state section. `'national'` reports `usnat` for every US visitor; the MSPA reserves it for signatories that chose the national approach. |
114
+ | `usFallback` | `'usnat'` | Under the state approach, the section for a US visitor whose state is unknown or has no section: `'usnat'` or `'none'`. |
115
+ | `mspaMode` | unset | `'opt-out-option'` or `'service-provider'`. Set it only if you signed the IAB Multi-State Privacy Agreement; the transaction is then reported as covered. In service provider mode the opt-outs are reported as not applicable. |
116
+ | `optOutCategories` | `['marketing']` | Categories whose refusal reports an opt-out of sale, sharing and targeted advertising. |
117
+ | `tcf` | `true` | Include the `tcfeuv2` section under an `iab` policy. It needs IAB TCF set up as well. |
118
+
119
+ ## Check the signal
120
+
121
+ Open the browser console on a page with GPP on and call:
122
+
123
+ ```js
124
+ __gpp('ping', (data) => console.log(data));
125
+ ```
126
+
127
+ For a California visitor who has not opted out, the result includes
128
+ `applicableSections: [8]`, `signalStatus: 'ready'` and
129
+ `parsedSections.usca[0].SaleOptOut === 2`. After the visitor refuses
130
+ `marketing`, `SaleOptOut` is `1`. `__gpp('getField', cb, 'usca.SaleOptOut')`
131
+ returns the same value. Scripts in iframes reach the API through the
132
+ `__gppLocator` frame and `postMessage`.
133
+
134
+ Under an `iab` rule, `parsedSections.tcfeuv2` uses the field names of the
135
+ IAB Europe TCF section, such as `PurposeConsent`, `VendorConsent` and
136
+ `PubRestrictions`. `getSection` and `getField` answer `null` for `tcfeuv2`,
137
+ as the IAB reference implementation does: TCF vendors read consent from
138
+ events, not on demand.
139
+
140
+ `signalStatus` stays `'not ready'` while the policy is pending, while the
141
+ banner or dialog is open, while an `iab` rule has no confirmed TC String,
142
+ and while a new TC String is decoded. Listeners added with
143
+ `addEventListener` get `signalStatus: 'not ready'`, then `cmpDisplayStatus`
144
+ and `sectionChange` events, then `signalStatus: 'ready'`.
145
+
146
+ ## Ad tags that load before c15t
147
+
148
+ c15t installs `__gpp` in the browser once consent starts, after it loads the
149
+ GPP code. Vendor scripts that call `__gpp` earlier need a stub. c15t takes over
150
+ the calls and listeners a stub queued, whether the stub keeps listeners on
151
+ `__gpp.events`, as the GPP specification's sample does, or returns them from
152
+ `__gpp('events')`, as `@iabgpp/stub` does. Call `initializeGPPStub()` from
153
+ `@c15t/iab/gpp` in your first script, or use the IAB's own stub snippet.
154
+ When another stub already answers iframes, c15t leaves that to it, so each
155
+ iframe call gets one reply.
156
+
157
+ If a CMP that has already loaded owns `__gpp`, c15t leaves it in place.
158
+ The `gpp` option reports the conflict to the `onError` callback,
159
+ `ConsentGPP` logs it, and `mountGPP()` and `createGPP()` throw. Run one GPP
160
+ CMP per page.
161
+
162
+ GPP signals what the visitor chose; it does not block scripts. Gate vendor
163
+ scripts with c15t's script loading as well.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  title: PostHog
3
3
  description: Load PostHog before or after measurement consent with the c15t
4
- posthog helper, choose its cookieless behavior, and sync consent with an SDK
5
- you already initialize.
4
+ posthog helper, choose its cookieless behavior, turn off PostHog modules you
5
+ do not use, and sync consent with an SDK you already initialize.
6
6
  icon: posthog
7
7
  group: integrations
8
8
  ---
@@ -257,15 +257,20 @@ A kernel you create yourself needs a loader from
257
257
 
258
258
  ## Options
259
259
 
260
- | Option | Default | Behavior |
261
- | ------------- | ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
262
- | `id` | Required | Project token, starting with `phc_`. The helper trims it. Empty or whitespace-only values throw. |
263
- | `region` | `'eu'` | `'eu'` or `'us'`. Picks the API, UI and loader hosts when you do not set them. |
264
- | `apiHost` | `https://eu.i.posthog.com` | API host for a proxy or self-hosted PostHog. Without `scriptUrl`, the loader URL becomes `<apiHost>/static/array.js`. |
265
- | `uiHost` | The region's UI host | UI host, for example `https://eu.posthog.com`. A custom `apiHost` with no `region` uses the API host. |
266
- | `scriptUrl` | `https://eu-assets.i.posthog.com/static/array.js` | Loader URL override. A blank value falls back to the default. |
267
- | `loadMode` | `'always'` | When the SDK loads. See the table below. |
268
- | `initOptions` | `{ cookieless_mode: 'on_reject', defaults: '2026-01-30' }` | Options passed to `posthog.init`, merged over the defaults. The helper sets `api_host` and `ui_host` after your values, so change hosts with the options above. |
260
+ | Option | Default | Behavior |
261
+ | ----------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
262
+ | `id` | Required | Project token, starting with `phc_`. The helper trims it. Empty or whitespace-only values throw. |
263
+ | `region` | `'eu'` | `'eu'` or `'us'`. Picks the API, UI and loader hosts when you do not set them. |
264
+ | `apiHost` | `https://eu.i.posthog.com` | API host for a proxy or self-hosted PostHog. Without `scriptUrl`, the loader URL becomes `<apiHost>/static/array.js`. |
265
+ | `uiHost` | The region's UI host | UI host, for example `https://eu.posthog.com`. A custom `apiHost` with no `region` uses the API host. |
266
+ | `scriptUrl` | `https://eu-assets.i.posthog.com/static/array.js` | Loader URL override. A blank value falls back to the default. |
267
+ | `loadMode` | `'always'` | When the SDK loads. See the table below. |
268
+ | `features.surveys` | Unset | `false` sets `disable_surveys: true` and skips `surveys.js`. See [turn off features you do not use](#turn-off-features-you-do-not-use). |
269
+ | `features.heatmaps` | Unset, follows the project | Sets `capture_heatmaps`. |
270
+ | `features.deadClicks` | Unset, follows the project | Sets `capture_dead_clicks`. |
271
+ | `features.webVitals` | Unset, follows the project | Sets `capture_performance: { web_vitals }`. Replay network timing keeps following the project. |
272
+ | `features.featureFlags` | Unset, follows the project | `false` sets `advanced_disable_feature_flags: true` and stops `/flags` requests. |
273
+ | `initOptions` | `{ cookieless_mode: 'on_reject', defaults: '2026-01-30' }` | Options passed to `posthog.init`, merged over the defaults and `features`. The helper sets `api_host` and `ui_host` after your values, so change hosts with the options above. |
269
274
 
270
275
  ## Loading and revocation
271
276
 
@@ -283,6 +288,78 @@ project; see
283
288
  Set `cookieless_mode: 'never'`, as in the example, if a refusal must stop
284
289
  capture.
285
290
 
291
+ ## Turn off features you do not use
292
+
293
+ After `array.js` loads, PostHog reads your `posthog.init` options and the
294
+ project's remote config, then decides which extra modules to download and
295
+ whether to request `/flags`. Set a `features` switch to `false` for each
296
+ feature your site does not use:
297
+
298
+ ```ts
299
+ posthog({
300
+ id: 'phc_YOUR_PROJECT_TOKEN',
301
+ region: 'eu',
302
+ loadMode: 'after-consent',
303
+ features: {
304
+ surveys: false,
305
+ heatmaps: false,
306
+ deadClicks: false,
307
+ webVitals: false,
308
+ featureFlags: false,
309
+ },
310
+ });
311
+ ```
312
+
313
+ | Switch | `false` sets | What PostHog skips |
314
+ | -------------- | -------------------------------------------- | --------------------------------------------------------------------------- |
315
+ | `surveys` | `disable_surveys: true` | `surveys.js`, about 29 KB |
316
+ | `heatmaps` | `capture_heatmaps: false` | Heatmap capture. With `deadClicks: false` too, `dead-clicks-autocapture.js` |
317
+ | `deadClicks` | `capture_dead_clicks: false` | `dead-clicks-autocapture.js`, about 8 KB, once heatmaps are off too |
318
+ | `webVitals` | `capture_performance: { web_vitals: false }` | `web-vitals-with-attribution.js`, about 6 KB |
319
+ | `featureFlags` | `advanced_disable_feature_flags: true` | `/flags` requests. The remote config still loads |
320
+
321
+ Sizes are brotli-compressed, measured from posthog-js 1.436.1.
322
+
323
+ An unset switch adds nothing to `posthog.init`. For heatmaps, dead clicks, web
324
+ vitals and feature flags, PostHog then follows the setting in your PostHog
325
+ project. `true` sets the opposite value and overrides the project. For
326
+ `surveys` and `featureFlags`, `true` is PostHog's own default, so it behaves
327
+ the same as unset.
328
+
329
+ Surveys work differently. PostHog downloads `surveys.js` whenever the remote
330
+ config includes a `surveys` value, even when surveys are off in the project.
331
+ `surveys: false` is the only way to skip it.
332
+
333
+ `initOptions` wins over a switch that sets the same key. A
334
+ `capture_performance` value in `initOptions` replaces the whole object that
335
+ `webVitals` builds.
336
+
337
+ ### Avoid PostHog's option traps
338
+
339
+ These rules also apply when you pass PostHog options yourself, in
340
+ `initOptions` or in your own `posthog.init`:
341
+
342
+ * Use `advanced_disable_feature_flags: true` to stop `/flags`, not
343
+ `advanced_disable_flags: true`. The second also stops PostHog loading the
344
+ remote config, so session replay never starts and other features fall back to
345
+ local config.
346
+ * With feature flags off, surveys that target a feature flag never show.
347
+ PostHog logs a warning about it. If you need those surveys, leave
348
+ `featureFlags` unset and pass
349
+ `advanced_only_evaluate_survey_feature_flags: true` instead. PostHog still
350
+ requests `/flags`, but evaluates only survey flags.
351
+ * While heatmaps are on, in `posthog.init` or in the project, PostHog loads
352
+ `dead-clicks-autocapture.js` whatever `capture_dead_clicks` says. Turn off
353
+ both to skip it.
354
+ * `capture_performance: false` turns off web vitals and session replay network
355
+ timing. `{ web_vitals: false }`, which `webVitals: false` sets, leaves network
356
+ timing to the project. `capture_performance: true` forces both on.
357
+
358
+ Product tours (`product-tours.js`, about 36 KB) and exception autocapture
359
+ (`exception-autocapture.js`, about 6 KB) load only when the project turns them
360
+ on. To keep them off whatever the project says, pass
361
+ `disable_product_tours: true` or `capture_exceptions: false` in `initOptions`.
362
+
286
363
  ## Guard your own capture calls
287
364
 
288
365
  Events your code sends need their own permission check. The `posthog` global
@@ -338,6 +415,11 @@ measurement is denied, so the SDK is told to opt out. `onConsentChange` runs on
338
415
  every later change. This does not delay the SDK import or its first request.
339
416
  `loadMode: 'disabled'` is not a substitute, because it syncs nothing.
340
417
 
418
+ `features` belongs to the helper, so it does nothing here. To skip modules you
419
+ do not use, pass the PostHog options from
420
+ [turn off features you do not use](#turn-off-features-you-do-not-use) to your
421
+ own `posthog.init`, for example `disable_surveys: true`.
422
+
341
423
  ## Measure opt-in rate
342
424
 
343
425
  If you run a banner experiment, the backend already counts visitors and
@@ -355,6 +437,12 @@ With `loadMode: 'always'`, `array.js` loads before a choice instead. Check that
355
437
  no capture request is sent while measurement is denied, unless you chose
356
438
  cookieless capture.
357
439
 
440
+ If you turned features off, allow measurement and filter the DevTools Network
441
+ panel by `posthog`. Reload the page. You should see `array.js`, the remote
442
+ config and capture requests, but no `/flags` request with `featureFlags: false`
443
+ and none of the modules you turned off, such as `surveys.js` or
444
+ `dead-clicks-autocapture.js`.
445
+
358
446
  Test in a private window with an opt-in policy. Open DevTools Network, disable
359
447
  the cache and filter by the vendor's domain:
360
448