@picked/client 0.0.70-next.6 → 0.0.71-next.1

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 (177) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/api-CH2FNa6S.cjs +1 -0
  3. package/dist/api-CgqRvuKL.js +38 -0
  4. package/dist/cjs/form/index.js +1 -1
  5. package/dist/cjs/index.js +2 -2
  6. package/dist/cjs/plugins/dom-observer/index.js +1 -1
  7. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  8. package/dist/cjs/server/index.js +1 -1
  9. package/dist/docs/actions.mdx +38 -18
  10. package/dist/docs/api/authentication.md +75 -48
  11. package/dist/docs/api/authorization-and-scopes.md +63 -47
  12. package/dist/docs/api/error-handling.md +70 -54
  13. package/dist/docs/api/index.md +41 -22
  14. package/dist/docs/api/pagination.md +30 -20
  15. package/dist/docs/api-keys.mdx +11 -6
  16. package/dist/docs/chrome-extension.md +22 -5
  17. package/dist/docs/conditions.mdx +32 -19
  18. package/dist/docs/content/asset-library.mdx +65 -34
  19. package/dist/docs/content/content-entries.mdx +8 -2
  20. package/dist/docs/content/layouts.mdx +45 -29
  21. package/dist/docs/content/pages.mdx +29 -14
  22. package/dist/docs/content/sections.mdx +19 -8
  23. package/dist/docs/delivery-api/experience-content.md +10 -5
  24. package/dist/docs/delivery-api/index.md +19 -12
  25. package/dist/docs/delivery-api/preview-caching-crawling.md +27 -14
  26. package/dist/docs/delivery-api/project-config.md +6 -4
  27. package/dist/docs/experiences.mdx +26 -10
  28. package/dist/docs/features.mdx +66 -25
  29. package/dist/docs/fields.mdx +42 -27
  30. package/dist/docs/flow-builder/building-flows.md +38 -20
  31. package/dist/docs/flow-builder/condition-builder.md +32 -21
  32. package/dist/docs/flow-builder/keyboard-shortcuts.md +18 -18
  33. package/dist/docs/flow-builder/nodes.md +26 -13
  34. package/dist/docs/flow-builder/overview.mdx +28 -15
  35. package/dist/docs/gtm-template.md +35 -26
  36. package/dist/docs/guides/build-a-personalized-page.mdx +36 -18
  37. package/dist/docs/image-optimisation-service.md +33 -17
  38. package/dist/docs/index.md +28 -17
  39. package/dist/docs/mcp.md +46 -38
  40. package/dist/docs/pickedql/data-sources.md +65 -43
  41. package/dist/docs/pickedql/dimensions.md +27 -12
  42. package/dist/docs/pickedql/examples.md +38 -26
  43. package/dist/docs/pickedql/filtering.md +10 -6
  44. package/dist/docs/pickedql/index.md +32 -14
  45. package/dist/docs/pickedql/schema.md +85 -76
  46. package/dist/docs/pickedql/virtual-functions.md +62 -36
  47. package/dist/docs/projects.mdx +14 -7
  48. package/dist/docs/reports.mdx +5 -3
  49. package/dist/docs/sdk/actions.md +33 -17
  50. package/dist/docs/sdk/conditions.md +8 -5
  51. package/dist/docs/sdk/content-security-policy.md +5 -4
  52. package/dist/docs/sdk/experiences.md +29 -11
  53. package/dist/docs/sdk/feature-allocation.md +79 -47
  54. package/dist/docs/sdk/features.md +130 -76
  55. package/dist/docs/sdk/fields.md +66 -42
  56. package/dist/docs/sdk/index.md +22 -13
  57. package/dist/docs/sdk/plugins/create-experience.md +18 -15
  58. package/dist/docs/sdk/plugins/dynamic-experiences.md +42 -16
  59. package/dist/docs/sdk/predicates.md +53 -37
  60. package/dist/docs/sdk/ssr/index.md +25 -8
  61. package/dist/docs/sdk/ssr/rendering-and-hydration.md +37 -16
  62. package/dist/docs/sdk/ssr/serialize.md +38 -20
  63. package/dist/docs/sdk/ssr/slots-and-placements.md +28 -10
  64. package/dist/docs/sdk/steps.md +100 -55
  65. package/dist/docs/steps.mdx +20 -11
  66. package/dist/docs/team-management.md +17 -11
  67. package/dist/docs/vite-plugin.md +26 -19
  68. package/dist/docs/workspace-versioning.mdx +14 -8
  69. package/dist/docs/workspaces.mdx +55 -24
  70. package/dist/{dom-BwmTHeT7.js → dom-BqcToNL0.js} +1 -3
  71. package/dist/{dom-CDrHc_j9.cjs → dom-DlLH3FxV.cjs} +1 -1
  72. package/dist/esm/form/index.js +2 -4
  73. package/dist/esm/index.js +50 -45
  74. package/dist/esm/plugins/dom-observer/index.js +1 -1
  75. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  76. package/dist/esm/preview/index.js +1 -1
  77. package/dist/esm/server/index.js +12 -10
  78. package/dist/{experience-instance-DOJShRk5.js → experience-instance-DGk11d8a.js} +3 -3
  79. package/dist/experience-instance-Dej3P1sE.cjs +1 -0
  80. package/dist/{plugin-BxMR7fOV.js → plugin-D2UqFikQ.js} +2 -2
  81. package/dist/plugin-DCW-tnjO.cjs +1 -0
  82. package/dist/{preview-channel-DW9J8t4B.js → preview-channel-CSr96XAo.js} +1 -1
  83. package/dist/{protocol-DSqOlW2r.js → protocol-C5KkkiZc.js} +1 -1
  84. package/dist/types/client.d.ts +7 -6
  85. package/dist/types/entities/action/action.d.ts +5 -4
  86. package/dist/types/entities/action/action.d.ts.map +1 -1
  87. package/dist/types/entities/component/component.d.ts +6 -5
  88. package/dist/types/entities/component/component.d.ts.map +1 -1
  89. package/dist/types/entities/condition/condition.d.ts +5 -5
  90. package/dist/types/entities/content/content.d.ts +4 -3
  91. package/dist/types/entities/experience/experience.d.ts +11 -10
  92. package/dist/types/entities/experience/experience.d.ts.map +1 -1
  93. package/dist/types/entities/experience-instance/experience-instance.d.ts +10 -9
  94. package/dist/types/entities/feature/feature.d.ts +3 -2
  95. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  96. package/dist/types/entities/field/field.d.ts +7 -6
  97. package/dist/types/entities/framework/framework.d.ts +7 -6
  98. package/dist/types/entities/framework/framework.d.ts.map +1 -1
  99. package/dist/types/entities/history/history.d.ts +6 -6
  100. package/dist/types/entities/plugin/plugin.d.ts +3 -2
  101. package/dist/types/entities/plugin/plugin.d.ts.map +1 -1
  102. package/dist/types/entities/predicate/predicate.d.ts +4 -3
  103. package/dist/types/entities/predicate/predicate.d.ts.map +1 -1
  104. package/dist/types/entities/response/response-validation.d.ts +3 -2
  105. package/dist/types/entities/response/response.d.ts +12 -11
  106. package/dist/types/entities/session/session.d.ts +2 -1
  107. package/dist/types/entities/step/step.d.ts +6 -5
  108. package/dist/types/exports.d.ts +2 -1
  109. package/dist/types/exports.d.ts.map +1 -1
  110. package/dist/types/form/build-responses.d.ts +2 -1
  111. package/dist/types/form/create-field-evaluator.d.ts +4 -3
  112. package/dist/types/form/create-field-validation.d.ts +5 -4
  113. package/dist/types/form/create-form.d.ts +7 -6
  114. package/dist/types/form/create-submit-pipeline.d.ts +7 -6
  115. package/dist/types/form/get-step-fields.d.ts +3 -2
  116. package/dist/types/lifecycle/condition-selection.d.ts +14 -4
  117. package/dist/types/lifecycle/lifecycle.d.ts +4 -3
  118. package/dist/types/lifecycle/rules-logic.d.ts +4 -3
  119. package/dist/types/lifecycle/state-snapshot.d.ts +4 -3
  120. package/dist/types/picked.d.ts +64 -62
  121. package/dist/types/picked.d.ts.map +1 -1
  122. package/dist/types/ping.d.ts +1 -1
  123. package/dist/types/ping.d.ts.map +1 -1
  124. package/dist/types/plugins/create-experience/create-experience.d.ts +9 -8
  125. package/dist/types/plugins/create-experience/create-experience.d.ts.map +1 -1
  126. package/dist/types/plugins/create-experience/plugin.d.ts +3 -2
  127. package/dist/types/plugins/create-experience/plugin.d.ts.map +1 -1
  128. package/dist/types/plugins/create-experience/serialization.d.ts.map +1 -1
  129. package/dist/types/plugins/dom-observer/plugin.d.ts +1 -1
  130. package/dist/types/plugins/dynamic-experiences/plugin.d.ts +3 -3
  131. package/dist/types/plugins/dynamic-experiences/types.d.ts +8 -5
  132. package/dist/types/plugins/experience-browser-storage/plugin.d.ts +1 -1
  133. package/dist/types/plugins/experience-browser-storage/plugin.d.ts.map +1 -1
  134. package/dist/types/plugins/history-browser-storage/plugin.d.ts +1 -1
  135. package/dist/types/plugins/history-browser-storage/plugin.d.ts.map +1 -1
  136. package/dist/types/plugins/responses-browser-storage/plugin.d.ts +1 -1
  137. package/dist/types/plugins/responses-browser-storage/plugin.d.ts.map +1 -1
  138. package/dist/types/plugins/responses-indexeddb/plugin.d.ts +1 -1
  139. package/dist/types/plugins/single-page-app/plugin.d.ts +1 -1
  140. package/dist/types/plugins/single-page-app/plugin.d.ts.map +1 -1
  141. package/dist/types/plugins/web-components/plugin.d.ts +2 -1
  142. package/dist/types/plugins/window-object/plugin.d.ts +2 -2
  143. package/dist/types/preview-channel.d.ts +5 -1
  144. package/dist/types/preview-receiver/index.d.ts +18 -1
  145. package/dist/types/preview-receiver/protocol.d.ts +10 -1
  146. package/dist/types/server/index.d.ts +3 -3
  147. package/dist/types/server/serialize.d.ts +2 -2
  148. package/dist/types/utils/dom.d.ts +2 -1
  149. package/dist/types/utils/events.d.ts +6 -6
  150. package/dist/types/utils/events.d.ts.map +1 -1
  151. package/dist/types/utils/global-obj.d.ts +1 -1
  152. package/dist/types/utils/global-obj.d.ts.map +1 -1
  153. package/dist/types/utils/id.d.ts.map +1 -1
  154. package/dist/types/utils/logger.d.ts +2 -1
  155. package/dist/types/utils/logger.d.ts.map +1 -1
  156. package/dist/types/utils/node-tree/get-next-node-from-action.d.ts +2 -1
  157. package/dist/types/utils/node-tree/get-next-node-from-condition.d.ts +2 -1
  158. package/dist/types/utils/node-tree/get-next-node.d.ts +1 -1
  159. package/dist/types/utils/node-tree/get-next-node.d.ts.map +1 -1
  160. package/dist/types/utils/node-tree/get-origin-node.d.ts +1 -1
  161. package/dist/types/utils/node-tree/get-origin-node.d.ts.map +1 -1
  162. package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts +1 -1
  163. package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts.map +1 -1
  164. package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts +1 -1
  165. package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts.map +1 -1
  166. package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts +1 -1
  167. package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts.map +1 -1
  168. package/dist/types/utils/nodes.d.ts +1 -1
  169. package/dist/types/utils/picked-config.d.ts +2 -1
  170. package/dist/types/utils/rules-logic.d.ts +6 -2
  171. package/dist/types/utils/storage.d.ts +1 -1
  172. package/dist/types/utils/storage.d.ts.map +1 -1
  173. package/package.json +22 -23
  174. package/dist/api-CoUkIxIr.cjs +0 -1
  175. package/dist/api-ahdSBQ3Y.js +0 -40
  176. package/dist/experience-instance-DurC3pE-.cjs +0 -1
  177. package/dist/plugin-CASdGtTX.cjs +0 -1
@@ -4,29 +4,40 @@ layout: docs
4
4
 
5
5
  # Feature flags
6
6
 
7
- Feature flags let you run A/B tests and roll changes out gradually. Manage them from the dashboard, or define them in SDK code when you need your own decision logic.
7
+ A feature flag lets you run an A/B test and roll a change out in stages. Control a
8
+ feature flag from the dashboard, or define it in SDK code when you need your own decision
9
+ rules.
8
10
 
9
11
  ## Where feature flags come from
10
12
 
11
- Most feature flags are managed from the dashboard. The [dynamic experiences plugin](/sdk/plugins/dynamic-experiences) fetches your project configuration at runtime and registers every feature flag automatically — you do not declare them in code.
13
+ You control most feature flags from the dashboard. The
14
+ [dynamic experiences plugin](/sdk/plugins/dynamic-experiences) reads the options of
15
+ your project at runtime and registers every feature flag for you. You declare no feature flag in
16
+ code.
12
17
 
13
- For a dashboard-managed feature flag, the SDK generates the definition:
18
+ For a feature flag that you control from the dashboard, the SDK makes the definition:
14
19
 
15
20
  | Field | Value |
16
21
  | --- | --- |
17
- | `id` | The feature flag's dashboard key |
18
- | `key` | `v{version}` — for example `v3` |
19
- | `name` | The feature flag's dashboard name |
22
+ | `id` | The dashboard key of the feature flag |
23
+ | `key` | `v{version}`, for example `v3` |
24
+ | `name` | The dashboard name of the feature flag |
20
25
  | `enabled` | The dashboard **Enabled** toggle |
21
- | `weight` | The dashboard **Weight**, 0–100 |
26
+ | `weight` | The dashboard **Weight**, 0 to 100 |
22
27
  | `storage` | `local`, or `session` in [preview mode](/sdk/feature-allocation#preview-mode) |
23
- | `decide` | Generated — allocates by comparing the visitor's stable value for this feature flag against `weight` |
28
+ | `decide` | Made by the SDK. It compares the stable value of the visitor for this feature flag against `weight`. |
24
29
 
25
- The generated `decide` derives from the visitor ID, the feature flag ID, and the weight alone, so a dashboard-managed feature flag cannot base its decision on your application's data. Define the feature flag in SDK code when you need that.
30
+ The `decide` function that the SDK makes derives from the visitor ID, the ID of the
31
+ feature flag, and the weight. A feature flag that you control from the dashboard
32
+ therefore cannot use the data of your application. Define the feature flag in SDK code
33
+ when you need that.
26
34
 
27
- ## Defining feature flags in SDK code
35
+ ## Define a feature flag in SDK code
28
36
 
29
- Define feature flags when creating the Picked client by passing a `features` object. Use this when a decision depends on data only your application has. Each feature flag requires a unique identifier, a versioning key, a human-readable name, and a `decide` function:
37
+ Define your feature flags when you create the Picked client. Pass a `features` object. Use
38
+ this when a decision needs data that only your application holds. Each feature flag needs a
39
+ unique identifier, a version key, a name that a person can read, and a `decide`
40
+ function:
30
41
 
31
42
  ```ts
32
43
  import { createClient } from '@picked/client'
@@ -35,20 +46,20 @@ const client = createClient({
35
46
  projectId: 'your-project-id',
36
47
  features: {
37
48
  'summer-sale': {
38
- key: 'summer-sale-v1', // Version key - change to invalidate cache
39
- name: 'Summer Sale Experiment', // Human-readable name for analytics
49
+ key: 'summer-sale-v1', // The version key. Change it to invalidate the cache.
50
+ name: 'Summer Sale Experiment', // A name for analytics that a person can read.
40
51
  decide: async () => {
41
- // Your decision logic here
52
+ // Put your decision rules here.
42
53
  const isEligible = await checkUserEligibility()
43
- return isEligible // Returns boolean
54
+ return isEligible // Returns a boolean.
44
55
  },
45
- storage: 'local', // Optional: 'local' (default) or 'session'
56
+ storage: 'local', // Optional: 'local' is the default, or 'session'.
46
57
  },
47
58
  'homepage-variant': {
48
59
  key: 'homepage-test-v2',
49
60
  name: 'Homepage A/B Test',
50
61
  decide: async () => {
51
- // Return string for multi-variant tests
62
+ // Return a string for a test of more than two variants.
52
63
  return Math.random() < 0.5 ? 'variant-a' : 'variant-b'
53
64
  },
54
65
  },
@@ -56,7 +67,8 @@ const client = createClient({
56
67
  })
57
68
  ```
58
69
 
59
- Feature flags can also be defined as factory functions for lazy initialization:
70
+ A feature flag can also be a factory function, which starts the feature flag only when it is
71
+ first needed:
60
72
 
61
73
  ```ts
62
74
  features: {
@@ -70,25 +82,33 @@ features: {
70
82
 
71
83
  ## How feature flags work
72
84
 
73
- When a feature flag is registered, the SDK follows this execution order:
85
+ After you register a feature flag, the SDK follows this order:
74
86
 
75
- 1. **Registration** — feature flags are registered with the client at startup. Registering does not allocate anyone.
76
- 2. **Cache check** — the SDK looks in browser storage for a decision from a previous visit, keyed by the feature flag `id` and `key`. If one exists the feature flag starts out already resolved, which is why a returning visitor is allocated before anything reads the feature flag.
77
- 3. **Lazy evaluation** — with no cached decision, `decide` runs the first time something reads the bucket.
78
- 4. **Storage** — the resolved bucket is written to browser storage.
79
- 5. **Event emission** — a `feature_decide` event fires, but only for a decision made during this page view. A decision restored in step 2 emits nothing.
87
+ 1. **Registration** — the client registers each feature flag at startup. A registration
88
+ allocates nobody.
89
+ 2. **Cache check** — the SDK looks in browser storage for a decision from an earlier
90
+ visit. The cache key holds the feature flag `id` and `key`. When an entry is there, the
91
+ feature flag starts in a resolved state. This is why Picked allocates a visitor who
92
+ returns before anything reads the feature flag.
93
+ 3. **Lazy evaluation** — with no cached decision, `decide` runs the first time that
94
+ something reads the bucket.
95
+ 4. **Storage** — the SDK writes the resolved bucket to browser storage.
96
+ 5. **Event emission** — the SDK sends a `feature_decide` event, but only for a
97
+ decision that it makes during this page view. A decision that step 2 restores
98
+ sends nothing.
80
99
 
81
- See [Allocation and exposure](/sdk/feature-allocation) for what this means when tracking exposures.
100
+ See [Allocation and exposure](/sdk/feature-allocation) for what this means when you
101
+ track an exposure.
82
102
 
83
- ## URL-based overrides
103
+ ## Overrides in the URL
84
104
 
85
- You can override feature flag decisions via URL search parameters for testing purposes:
105
+ You can override a feature flag decision from the URL search parameters, for a test:
86
106
 
87
107
  ```
88
108
  https://example.com?picked-feature=my-feature:true
89
109
  ```
90
110
 
91
- Multiple overrides can be specified:
111
+ You can give more than one override value:
92
112
 
93
113
  ```
94
114
  https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
@@ -96,11 +116,15 @@ https://example.com?picked-feature=my-feature:true&picked-feature=homepage-varia
96
116
 
97
117
  The format is `?picked-feature=<feature-id>:<bucket-value>`.
98
118
 
99
- This is useful for QA testing specific bucket values without modifying code. The override is persisted in session storage and will be cleared when the browser tab is closed.
119
+ This is useful for QA, because you can test one bucket value and change no code. The
120
+ SDK keeps the override value in session storage. The browser clears it when you close
121
+ the tab.
100
122
 
101
- ## Accessing feature flags
123
+ ## Read a feature flag
102
124
 
103
- Once feature flags are defined, you can read their resolved bucket values in your application code. The SDK provides framework hooks and a framework-agnostic event bus.
125
+ After you define your feature flags, your application code can read the resolved
126
+ bucket value of each one. The SDK gives you a hook for each framework, and an event
127
+ bus that works with any framework.
104
128
 
105
129
  :::code-group
106
130
 
@@ -108,7 +132,7 @@ Once feature flags are defined, you can read their resolved bucket values in you
108
132
  import { createClient } from '@picked/client'
109
133
 
110
134
  const client = createClient({
111
- /* Create client options here */
135
+ /* Put your client options here. */
112
136
  })
113
137
 
114
138
  client.eventBus.watch((event) => {
@@ -122,7 +146,7 @@ client.eventBus.watch((event) => {
122
146
  <script lang="ts">
123
147
  import { getFeatureResult } from '@picked/svelte/hooks'
124
148
 
125
- // A store holding { id, bucket }, or undefined until the flag resolves
149
+ // A store that holds { id, bucket }, or undefined until the feature flag resolves.
126
150
  const result = getFeatureResult('my-feature')
127
151
  </script>
128
152
 
@@ -137,7 +161,7 @@ client.eventBus.watch((event) => {
137
161
  import { useFeatureResult } from '@picked/react/hooks'
138
162
 
139
163
  function MyComponent() {
140
- // { id, bucket }, or undefined until the flag resolves
164
+ // { id, bucket }, or undefined until the feature flag resolves.
141
165
  const result = useFeatureResult('my-feature')
142
166
 
143
167
  return (
@@ -151,41 +175,46 @@ function MyComponent() {
151
175
 
152
176
  :::
153
177
 
154
- ### Choosing an accessor
178
+ ### Choose an accessor
155
179
 
156
- Accessors differ in what they return, and in whether reading one resolves the feature flag. Resolving is what commits a visitor to a bucket and reports the decision — see [Allocation and exposure](/sdk/feature-allocation).
180
+ Each accessor returns a different value. They also differ in whether a read resolves
181
+ the feature flag. A read that resolves the feature flag commits the visitor to a bucket and reports
182
+ the decision. See [Allocation and exposure](/sdk/feature-allocation).
157
183
 
158
- | API | Returns | Resolves the feature flag? |
184
+ | API | Returns | Does a read resolve the feature flag? |
159
185
  | --- | --- | --- |
160
- | `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the returned function |
161
- | `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, `undefined` until resolved | Yes, as soon as the component renders |
162
- | `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined` — the dashboard **Enabled** toggle | No |
163
- | `client.features.listStore` | Every registered feature flag, each with an `isResolved` boolean | No |
164
- | `client.features.getById(id)` | A store holding a single feature flag | No |
186
+ | `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the function that it returns |
187
+ | `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, and `undefined` until it resolves | Yes, as soon as the component renders |
188
+ | `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined`, the dashboard **Enabled** toggle | No |
189
+ | `client.features.listStore` | Every registered feature flag, each with an `isResolved` feature flag | No |
190
+ | `client.features.getById(id)` | A store that holds one feature flag | No |
165
191
 
166
192
  :::warning
167
- `useFeatureResult` and `getFeatureResult` resolve the feature flag as soon as the component renders, committing the visitor to a bucket at that moment.
193
+ `useFeatureResult` and `getFeatureResult` resolve the feature flag as soon as the component
194
+ renders. They commit the visitor to a bucket at that moment.
168
195
 
169
- If the decision should wait for data you do not have yet — a cart, a quote, a signed-in profile — use `useFeature` or `getFeature` and call the function they return once that data arrives.
196
+ Some decisions must wait for data that you do not have yet, such as a cart, a quote,
197
+ or a profile of a signed-in user. For those, use `useFeature` or `getFeature`. Call
198
+ the function that they return after the data arrives.
170
199
  :::
171
200
 
172
- ## Using feature flags in experience conditions
201
+ ## Use a feature flag in an experience condition
173
202
 
174
- Feature flags can be used with experience conditions via JSON logic:
203
+ You can use a feature flag in an experience condition through JSON logic:
175
204
 
176
205
  ```ts
177
206
  const myExperience = createExperience({
178
- // ... other config
207
+ // The other options go here.
179
208
  condition: {
180
209
  and: [
181
210
  {
182
211
  '==': [
183
212
  {
184
213
  invoke: {
185
- var: 'features.summer-sale', // Reference the flag by ID
214
+ var: 'features.summer-sale', // Point at the feature flag by its ID.
186
215
  },
187
216
  },
188
- true, // Check if bucket equals true
217
+ true, // Check that the bucket equals true.
189
218
  ],
190
219
  },
191
220
  ],
@@ -193,13 +222,15 @@ const myExperience = createExperience({
193
222
  })
194
223
  ```
195
224
 
196
- The ID in `features.<id>` is the feature flag's **Key** from the dashboard — the same value shown on the feature flag detail page.
225
+ The ID in `features.<id>` is the **Key** of the feature flag from the dashboard. It is the
226
+ same value that the detail page of the feature flag shows.
197
227
 
198
- Evaluating a condition reads the bucket, so a feature flag referenced by an experience condition is allocated at the moment that condition runs.
228
+ An evaluation of a condition reads the bucket. Picked therefore allocates a visitor
229
+ to a feature flag when the condition that points at it runs.
199
230
 
200
- ## Listening to feature flag decisions
231
+ ## Watch for feature flag decisions
201
232
 
202
- Subscribe to the `feature_decide` event to track when feature flags are resolved:
233
+ Subscribe to the `feature_decide` event to track when a feature flag resolves:
203
234
 
204
235
  ```ts
205
236
  client.eventBus.watch((event) => {
@@ -207,9 +238,9 @@ client.eventBus.watch((event) => {
207
238
 
208
239
  const { feature } = event
209
240
  console.log(`Flag "${feature.id}" resolved to bucket:`, feature.bucket)
210
- // Example output: Flag "summer-sale" resolved to bucket: true
241
+ // Example output: the feature flag resolved to the bucket true.
211
242
 
212
- // Send to analytics
243
+ // Send the decision to your analytics.
213
244
  analytics.track('Feature Decision', {
214
245
  featureId: feature.id,
215
246
  bucket: feature.bucket,
@@ -217,50 +248,73 @@ client.eventBus.watch((event) => {
217
248
  })
218
249
  ```
219
250
 
220
- ## Versioning and cache invalidation
251
+ ## Versions and cache invalidation
221
252
 
222
- Feature flag decisions are cached in browser storage (localStorage, or sessionStorage in preview) under a key derived from the feature flag ID and version, so a visitor keeps the same decision across page loads.
253
+ The SDK caches a feature flag decision in browser storage. It uses `localStorage`, or
254
+ `sessionStorage` in a preview. The cache key derives from the feature flag ID and the version,
255
+ so a visitor keeps the same decision across page loads.
223
256
 
224
257
  :::warning
225
- Bucket values that are strings, such as `'variant-a'`, are not yet restored correctly from storage — the visitor is treated as unallocated on their next page load. Boolean bucket values, which includes every feature flag managed from the dashboard, are unaffected. Tracked in PICK-1574.
258
+ The SDK does not yet restore a string bucket value, such as `'variant-a'`, correctly
259
+ from storage. It treats the visitor as unallocated on the next page load. A boolean
260
+ bucket value is correct, and that includes every feature flag that you control from the
261
+ dashboard. See PICK-1574.
226
262
  :::
227
263
 
228
- ### How versioning works
264
+ ### How versions work
229
265
 
230
- When you create a feature flag in the dashboard, it starts at version 1. The SDK uses the version to build the cache key — for example, a feature flag at version 3 caches under `v3`.
266
+ A feature flag that you create in the dashboard starts at version 1. The SDK uses the
267
+ version to build the cache key. A feature flag at version 3 caches under `v3`.
231
268
 
232
- The version affects **caching only**. It is not part of the allocation calculation, which for dashboard-managed feature flags derives from the visitor ID, the feature flag ID, and the feature flag's weight.
269
+ The version affects **the cache only**. It is not part of the allocation. For a feature flag
270
+ that you control from the dashboard, the allocation derives from the visitor ID and
271
+ the weight of the feature flag.
233
272
 
234
- ### What happens when the version is incremented
273
+ ### What happens when you increment the version
235
274
 
236
- When you increment a feature flag's version in the dashboard:
275
+ When you increment the version of a feature flag in the dashboard:
237
276
 
238
- 1. **Cache keys change.** The old cache entry (e.g., `v3`) no longer matches the new key (`v4`), so the SDK treats the feature flag as undecided.
239
- 2. **The `decide` function runs again.** On the next page load, the SDK calls `decide()` fresh and caches the new result.
240
- 3. **Visitors are re-evaluated, not reshuffled.** For dashboard-managed feature flags allocation is deterministic, so re-evaluating the same visitor against an unchanged weight produces the same bucket.
277
+ 1. **The cache key changes.** The old entry, `v3`, no longer matches the new key,
278
+ `v4`. The SDK therefore treats the feature flag as undecided.
279
+ 2. **The `decide` function runs again.** On the next page load, the SDK calls
280
+ `decide()` again and caches the new result.
281
+ 3. **Picked evaluates each visitor again and moves nobody.** For a feature flag that you
282
+ control from the dashboard the allocation is deterministic. The same visitor
283
+ against an unchanged weight gives the same bucket.
241
284
 
242
285
  :::note
243
- Incrementing the version clears cached decisions and runs allocation again. It does not randomise visitors — with an unchanged weight, everyone lands where they were. Increment it after changing a weight, so visitors who already hold a cached decision are re-evaluated against the new one.
286
+ The new version clears the cached decisions and runs the allocation again. It moves
287
+ no visitor between groups. With an unchanged weight, everybody lands where they were.
288
+ Increment the version after you change a weight, so that Picked evaluates a visitor
289
+ with a cached decision against the new weight.
244
290
  :::
245
291
 
246
- ### SDK `key` vs dashboard version
292
+ ### The SDK `key` and the dashboard version
247
293
 
248
- If you define feature flags statically in the SDK (not loaded from the dashboard), use the `key` field for manual versioning:
294
+ For a feature flag that you define in SDK code, use the `key` field to set the version
295
+ yourself. This applies when you do not read the feature flag from the dashboard:
249
296
 
250
297
  ```ts
251
298
  features: {
252
299
  'summer-sale': {
253
- key: 'summer-sale-v2', // Change to invalidate cache
254
- // ...
300
+ key: 'summer-sale-v2', // Change this to invalidate the cache.
301
+ // The other options go here.
255
302
  },
256
303
  }
257
304
  ```
258
305
 
259
- For feature flags managed through the dashboard, versioning is handled automatically — the dashboard appends the version number to the cache key used by the SDK.
306
+ For a feature flag that you control from the dashboard, Picked sets the version for you. The
307
+ dashboard adds the version number to the end of the cache key that the SDK uses.
260
308
 
261
309
  ## Best practices
262
310
 
263
- - **Use `session` storage for temporary tests**: Set `storage: 'session'` for short-lived experiments that shouldn't persist across browser sessions
264
- - **Keep `decide` functions fast**: Anything awaiting the feature flag — an experience condition, a component reading the result — waits for `decide` to finish, so avoid slow network calls inside it
265
- - **Allocate when the decision matters**: Defer reading a feature flag until the data it depends on has arrived, rather than reading every feature flag at startup. See [Allocation and exposure](/sdk/feature-allocation)
266
- - **Return consistent types**: Don't mix boolean and string returns for the same feature flag - pick one type and stick with it
311
+ - **Use `session` storage for a short test**: set `storage: 'session'` for an
312
+ experiment that must not stay across browser sessions.
313
+ - **Keep each `decide` function fast**: everything that waits for the feature flag waits for
314
+ `decide` to finish. This includes an experience condition and a component that
315
+ reads the result. Do not make a slow network call inside it.
316
+ - **Allocate when the decision matters**: read a feature flag only after the data that it
317
+ needs arrives. Do not read every feature flag at startup. See
318
+ [Allocation and exposure](/sdk/feature-allocation).
319
+ - **Return one type**: do not return a boolean from a feature flag one time and a string the
320
+ next. Choose one type and keep it.
@@ -4,16 +4,19 @@ layout: docs
4
4
 
5
5
  # Fields
6
6
 
7
- Define fields in code with `createField()`, validate input with JSON Logic, and read submitted values through step context.
7
+ Define your fields in code with `createField()`. Check the input with JSON Logic.
8
+ Read the values that a user sends through the step context.
8
9
 
9
- ## Defining fields
10
+ ## Define a field
10
11
 
11
- Define fields using `createField()` from the `@picked/client/plugins/create-experience` package. Each field requires a unique identifier, name, type, label, and cardinality:
12
+ Define a field with `createField()` from the
13
+ `@picked/client/plugins/create-experience` package. Each field needs a unique
14
+ identifier, a name, a type, a label, and a count of the values that it takes:
12
15
 
13
16
  ```ts
14
17
  import { createField } from '@picked/client/plugins/create-experience'
15
18
 
16
- // Required text input
19
+ // A text input that the user must fill in.
17
20
  export const emailField = createField({
18
21
  id: 'email-address',
19
22
  name: 'email-address',
@@ -24,7 +27,7 @@ export const emailField = createField({
24
27
  placeholder: 'you@example.com',
25
28
  })
26
29
 
27
- // Multi-select dropdown with static options
30
+ // A dropdown that takes more than one choice, with fixed options.
28
31
  export const communicationMethods = createField({
29
32
  id: 'communication-methods',
30
33
  name: 'communication-methods',
@@ -54,21 +57,28 @@ export const communicationMethods = createField({
54
57
 
55
58
  ## How fields work
56
59
 
57
- When a field is registered and used in an experience:
58
-
59
- 1. **Registration**: Fields are registered to the experience during step creation via `.addField()`
60
- 2. **Condition Evaluation**: If the field has a `condition`, it's evaluated against the current context to determine visibility
61
- 3. **Rendering**: Visible fields are rendered in the experience UI with their label, placeholder, and options
62
- 4. **User Input**: User enters or selects a value, triggering client-side validation if specified
63
- 5. **Storage**: Valid responses are stored in the experience state under `responses[field.name]`
64
- 6. **Reference**: Field values can be referenced in subsequent field conditions, step conditions, or experience logic via `{ var: 'responses.field-name' }`
60
+ After you register a field and an experience uses it:
61
+
62
+ 1. **Registration**: you register a field on the experience while you create the
63
+ step, with `.addField()`.
64
+ 2. **Condition evaluation**: when the field has a `condition`, the SDK evaluates it
65
+ against the current context. The result decides whether the field is visible.
66
+ 3. **Rendering**: the SDK renders each visible field in the experience UI, with its
67
+ label, its placeholder, and its options.
68
+ 4. **User input**: the user enters or chooses a value. Validation then runs in the
69
+ browser, when you set a rule.
70
+ 5. **Storage**: the SDK stores a valid response in the experience state, under
71
+ `responses[field.name]`.
72
+ 6. **Reference**: a later condition can point at the value with
73
+ `{ var: 'responses.field-name' }`. This works in a field condition, in a step
74
+ condition, and in the logic of an experience.
65
75
 
66
76
  ## Field types
67
77
 
68
- Fields support four primitive types, `text`, `number`, `boolean` and `object`:
78
+ A field has one of four basic types: `text`, `number`, `boolean`, or `object`.
69
79
 
70
80
  ```ts
71
- // Single text input
81
+ // One text input.
72
82
  const nameField = createField({
73
83
  type: 'text',
74
84
  multiple: false,
@@ -76,7 +86,7 @@ const nameField = createField({
76
86
  })
77
87
  ```
78
88
 
79
- Provide an `options` array to allow multiple select:
89
+ Give an `options` array to let the user choose more than one value:
80
90
 
81
91
  ```ts
82
92
  const nameField = createField({
@@ -104,7 +114,7 @@ const nameField = createField({
104
114
 
105
115
  ## Conditions
106
116
 
107
- Fields and field options are compatible with condition logic:
117
+ A field and a field option both work with condition logic:
108
118
 
109
119
  ```ts
110
120
  const conditionalField = createField({
@@ -131,7 +141,8 @@ const conditionalField = createField({
131
141
 
132
142
  ## Field validation
133
143
 
134
- Validate user input using JSON Logic rules. The `$input` variable contains the current field value:
144
+ Check the input of a user with a JSON Logic rule. The `$input` variable holds the
145
+ current value of the field:
135
146
 
136
147
  ```ts
137
148
  const quantityField = createField({
@@ -147,7 +158,7 @@ const quantityField = createField({
147
158
  {
148
159
  and: [
149
160
  {
150
- // Input value is above 0
161
+ // The input value is above 0.
151
162
  '>': [
152
163
  {
153
164
  var: '$input',
@@ -156,7 +167,7 @@ const quantityField = createField({
156
167
  ],
157
168
  },
158
169
  {
159
- // Input value is equal or below 999
170
+ // The input value is 999 or below.
160
171
  '<=': [
161
172
  {
162
173
  var: '$input',
@@ -176,12 +187,13 @@ const quantityField = createField({
176
187
  ```
177
188
 
178
189
  :::tip
179
- The `catch`/`throw` pattern in JSON Logic validates that the condition evaluates to `true`. If it doesn't, the `throw` message is displayed to the user.
190
+ The `catch` and `throw` pattern in JSON Logic checks that the condition gives `true`.
191
+ When it does not, the SDK shows the `throw` message to the user.
180
192
  :::
181
193
 
182
194
  ## Field policies
183
195
 
184
- Control how field data is tracked and stored using privacy policies:
196
+ Use a privacy policy to control how Picked tracks and stores the data of a field:
185
197
 
186
198
  ```ts
187
199
  const sensitiveField = createField({
@@ -192,22 +204,25 @@ const sensitiveField = createField({
192
204
  multiple: false,
193
205
  required: true,
194
206
  policies: {
195
- doNotTrackValue: true, // Don't send value to analytics
196
- doNotStoreValue: true, // Don't persist value to browser storage
207
+ doNotTrackValue: true, // Do not send the value to analytics.
208
+ doNotStoreValue: true, // Do not keep the value in browser storage.
197
209
  },
198
210
  })
199
211
  ```
200
212
 
201
213
  **Privacy policies**:
202
214
 
203
- - `doNotTrackValue`: Excludes field value from analytics events (only field ID is tracked)
204
- - `doNotStoreValue`: Prevents field value from being persisted to browser storage
215
+ - `doNotTrackValue`: keeps the value of the field out of your analytics events. The
216
+ events carry the field ID only.
217
+ - `doNotStoreValue`: stops the SDK, so that it does not put the value in browser
218
+ storage.
205
219
 
206
- Use these policies for sensitive data like SSNs, credit cards, or passwords that should only exist client-side.
220
+ Use these policies for sensitive data such as a tax number, a credit card number, or
221
+ a password. That data must stay in the browser.
207
222
 
208
223
  ## Field metadata
209
224
 
210
- Enhance user experience with additional descriptive text:
225
+ Add more text to help the user:
211
226
 
212
227
  ```ts
213
228
  const passwordField = createField({
@@ -225,18 +240,19 @@ const passwordField = createField({
225
240
 
226
241
  **Metadata fields**:
227
242
 
228
- - `placeholder`: Hint text displayed in empty input fields
229
- - `description`: Brief explanation shown below the label
230
- - `helpText`: Additional guidance or requirements displayed near the field
243
+ - `placeholder`: the hint text that an empty input shows.
244
+ - `description`: a short explanation below the label.
245
+ - `helpText`: more guidance, or the requirements, next to the field.
231
246
 
232
- ## Accessing field values
247
+ ## Read a field value
233
248
 
234
- Field responses are stored in the experience state and can be accessed via the `responses` object:
249
+ The SDK stores each field response in the experience state. Read a response through
250
+ the `responses` object:
235
251
 
236
252
  :::code-group
237
253
 
238
254
  ```ts [In Conditions]
239
- // Reference in subsequent field conditions
255
+ // Point at the value from a later field condition.
240
256
  const sensitiveField = createField({
241
257
  condition: {
242
258
  '==': [
@@ -253,11 +269,19 @@ const sensitiveField = createField({
253
269
 
254
270
  ## Best practices
255
271
 
256
- - **Use semantic IDs and names**: Name fields descriptively (e.g., `email-address` not `field1`) for easier debugging and analytics
257
- - **Match type to data**: Use `number` for numeric values, not `text`, to enable proper validation and comparison
258
- - **Set appropriate `required` flags**: Only mark fields as required when truly necessary to reduce form abandonment
259
- - **Keep validation messages clear**: Error messages should tell users exactly how to fix the input
260
- - **Use conditional fields for progressive disclosure**: Show fields only when relevant based on prior responses
261
- - **Apply privacy policies to sensitive data**: Use `doNotTrackValue` and `doNotStoreValue` for PII, passwords, or financial data
262
- - **Provide helpful metadata**: Add `placeholder` and `description` to guide users, especially for complex or formatted inputs
263
- - **Keep option lists scannable**: For more than 5 options, consider providing search/filter functionality or breaking into multiple steps
272
+ - **Give each field a name that says what it holds**: use `email-address` and not
273
+ `field1`. It is then easier to debug the flow and to read your analytics.
274
+ - **Match the type to the data**: use `number` for a numeric value, and not `text`.
275
+ The SDK can then check and compare the value correctly.
276
+ - **Mark a field as required only when it is**: fewer required fields means fewer
277
+ users who leave the form.
278
+ - **Keep each validation message clear**: an error message must tell the user exactly
279
+ how to correct the input.
280
+ - **Show a field only when it is relevant**: use a condition on the field, and decide
281
+ it from the earlier responses.
282
+ - **Apply a privacy policy to sensitive data**: use `doNotTrackValue` and
283
+ `doNotStoreValue` for personal information, a password, or financial data.
284
+ - **Give helpful metadata**: add a `placeholder` and a `description` to help the
285
+ user, above all for an input with a format.
286
+ - **Keep an option list easy to read**: for more than 5 options, add a search or
287
+ break the field into more than one step.