@picked/client 0.0.70-next.5 → 0.0.70

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 (78) hide show
  1. package/dist/{api-DQ1DqG_F.js → api-CPy9G49C.js} +1 -1
  2. package/dist/api-Ce6xYI--.cjs +1 -0
  3. package/dist/cjs/index.js +2 -2
  4. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  5. package/dist/cjs/server/index.js +1 -1
  6. package/dist/docs/actions.mdx +38 -18
  7. package/dist/docs/api/authentication.md +75 -48
  8. package/dist/docs/api/authorization-and-scopes.md +63 -47
  9. package/dist/docs/api/error-handling.md +70 -54
  10. package/dist/docs/api/index.md +41 -22
  11. package/dist/docs/api/pagination.md +30 -20
  12. package/dist/docs/api-keys.mdx +11 -6
  13. package/dist/docs/chrome-extension.md +22 -5
  14. package/dist/docs/conditions.mdx +32 -19
  15. package/dist/docs/content/asset-library.mdx +65 -34
  16. package/dist/docs/content/content-entries.mdx +8 -2
  17. package/dist/docs/content/layouts.mdx +45 -29
  18. package/dist/docs/content/pages.mdx +29 -14
  19. package/dist/docs/content/sections.mdx +19 -8
  20. package/dist/docs/delivery-api/experience-content.md +10 -5
  21. package/dist/docs/delivery-api/index.md +19 -12
  22. package/dist/docs/delivery-api/preview-caching-crawling.md +27 -14
  23. package/dist/docs/delivery-api/project-config.md +6 -4
  24. package/dist/docs/experiences.mdx +26 -10
  25. package/dist/docs/features.mdx +75 -20
  26. package/dist/docs/fields.mdx +42 -27
  27. package/dist/docs/flow-builder/building-flows.md +38 -20
  28. package/dist/docs/flow-builder/condition-builder.md +32 -21
  29. package/dist/docs/flow-builder/keyboard-shortcuts.md +18 -18
  30. package/dist/docs/flow-builder/nodes.md +26 -13
  31. package/dist/docs/flow-builder/overview.mdx +28 -15
  32. package/dist/docs/gtm-template.md +35 -26
  33. package/dist/docs/guides/build-a-personalized-page.mdx +36 -18
  34. package/dist/docs/image-optimisation-service.md +33 -17
  35. package/dist/docs/index.md +28 -17
  36. package/dist/docs/mcp.md +46 -38
  37. package/dist/docs/pickedql/data-sources.md +65 -43
  38. package/dist/docs/pickedql/dimensions.md +27 -12
  39. package/dist/docs/pickedql/examples.md +38 -26
  40. package/dist/docs/pickedql/filtering.md +10 -6
  41. package/dist/docs/pickedql/index.md +32 -14
  42. package/dist/docs/pickedql/schema.md +85 -76
  43. package/dist/docs/pickedql/virtual-functions.md +62 -36
  44. package/dist/docs/projects.mdx +14 -7
  45. package/dist/docs/reports.mdx +5 -3
  46. package/dist/docs/sdk/actions.md +33 -17
  47. package/dist/docs/sdk/conditions.md +8 -5
  48. package/dist/docs/sdk/content-security-policy.md +5 -4
  49. package/dist/docs/sdk/experiences.md +29 -11
  50. package/dist/docs/sdk/feature-allocation.md +79 -47
  51. package/dist/docs/sdk/features.md +130 -76
  52. package/dist/docs/sdk/fields.md +66 -42
  53. package/dist/docs/sdk/index.md +22 -13
  54. package/dist/docs/sdk/plugins/create-experience.md +18 -15
  55. package/dist/docs/sdk/plugins/dynamic-experiences.md +42 -16
  56. package/dist/docs/sdk/predicates.md +53 -37
  57. package/dist/docs/sdk/ssr/index.md +25 -8
  58. package/dist/docs/sdk/ssr/rendering-and-hydration.md +37 -16
  59. package/dist/docs/sdk/ssr/serialize.md +38 -20
  60. package/dist/docs/sdk/ssr/slots-and-placements.md +28 -10
  61. package/dist/docs/sdk/steps.md +100 -55
  62. package/dist/docs/steps.mdx +20 -11
  63. package/dist/docs/team-management.md +17 -11
  64. package/dist/docs/vite-plugin.md +26 -19
  65. package/dist/docs/workspace-versioning.mdx +14 -8
  66. package/dist/docs/workspaces.mdx +55 -24
  67. package/dist/esm/index.js +257 -238
  68. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  69. package/dist/esm/server/index.js +1 -1
  70. package/dist/{plugin-EPx6sE_l.js → plugin-ChPkTj__.js} +58 -53
  71. package/dist/plugin-DHn11gn6.cjs +1 -0
  72. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  73. package/dist/types/plugins/dynamic-experiences/hash.d.ts +40 -5
  74. package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
  75. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  76. package/package.json +1 -1
  77. package/dist/api-B9koex39.cjs +0 -1
  78. package/dist/plugin-YbSSGrM7.cjs +0 -1
@@ -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.
@@ -4,13 +4,15 @@ layout: docs
4
4
 
5
5
  # Picked SDK
6
6
 
7
- The Picked SDK enables you to add dynamic, personalized experiences to your web applications with minimal code.
7
+ The Picked SDK lets you add a dynamic, personalized experience to your web
8
+ application with very little code.
8
9
 
9
10
  :::tip
10
- The SDK is framework-agnostic and works with React, Svelte, Vue, and vanilla JavaScript.
11
+ The SDK is framework-agnostic. It works with React, Svelte, Vue, and plain
12
+ JavaScript.
11
13
  :::
12
14
 
13
- ## Quick Start
15
+ ## Quick start
14
16
 
15
17
  :::code-group
16
18
 
@@ -41,18 +43,20 @@ const snapshot = await serialize(client, {
41
43
 
42
44
  :::
43
45
 
44
- The `@picked/client/server` entry holds the server-only helpers, such as `serialize()`, that produce an SSR snapshot. It is a separate entry point so browser bundles never include it. See [Server-Side Rendering](/sdk/ssr).
46
+ The `@picked/client/server` entry holds the helpers that run only on a server, such
47
+ as `serialize()`. Those helpers make an SSR snapshot. It is a separate entry point,
48
+ so a browser bundle never holds it. See [Server-Side Rendering](/sdk/ssr).
45
49
 
46
50
  :::tip
47
- If you’re running Picked in a browser environment, access the Picked client in the console with `window.Picked`
51
+ In a browser, you can reach the Picked client from the console with `window.Picked`.
48
52
  :::
49
53
 
50
- ## Precondition & Condition
54
+ ## Precondition and condition
51
55
 
52
56
  | Category | Question | Description |
53
57
  | ------------ | ---------------------- | --------------------------------------------------------------------------------------------- |
54
- | Precondition | Should Picked start? | Use for environment checks, page filtering, or feature flags that completely disable the SDK. |
55
- | Condition | Is this user eligible? | Use for user segmentation, A/B test enrollment, or targeting specific user groups. |
58
+ | Precondition | Must Picked start? | Use it to check the environment, to filter by page, or for a feature flag that turns the SDK off completely. |
59
+ | Condition | Is this user eligible? | Use it to divide your users into groups, to enter a user into an A/B test, or to reach one group of users. |
56
60
 
57
61
  ```ts
58
62
  const picked = createClient({
@@ -76,14 +80,19 @@ const picked = createClient({
76
80
  var: 'features.convincely-enabled',
77
81
  },
78
82
  },
79
- // rest of picked config
83
+ // The rest of the Picked options go here.
80
84
  })
81
85
  ```
82
86
 
83
87
  :::warning
84
- A precondition or condition that references a flag reads its bucket, which allocates the visitor at that moment. Preconditions run at startup, so a flag referenced there is allocated for every visitor before anything else happens — including visitors who never reach the experience it gates.
85
-
86
- See [Allocation and exposure](/sdk/feature-allocation) before referencing a flag in a precondition.
88
+ A precondition or a condition that points at a flag reads the bucket of that flag,
89
+ and that allocates the visitor at that moment. A precondition runs at startup.
90
+ Picked therefore allocates every visitor to a flag that a precondition points at,
91
+ before anything else happens. This includes a visitor who never reaches the
92
+ experience that the flag controls.
93
+
94
+ See [Allocation and exposure](/sdk/feature-allocation) before you point a
95
+ precondition at a flag.
87
96
  :::
88
97
 
89
- See [conditions](./conditions.md) for more detail
98
+ See [conditions](./conditions.md) for more detail.
@@ -4,9 +4,9 @@ layout: docs
4
4
 
5
5
  # Create Experience Plugin
6
6
 
7
- A plugin to register an array of experiences to the Picked client.
7
+ This plugin registers an array of experiences on the Picked client.
8
8
 
9
- ## Setup Plugin
9
+ ## Set the plugin up
10
10
 
11
11
  :::code-group
12
12
 
@@ -26,14 +26,15 @@ const client = createClient({
26
26
 
27
27
  :::
28
28
 
29
- ## Defining Steps
29
+ ## Define a step
30
30
 
31
- Steps are defined using `createStep()`. They are added directly to an experience using `experience.addStep()`.
31
+ Define a step with `createStep()`. Add it directly to an experience with
32
+ `experience.addStep()`.
32
33
 
33
34
  ```ts
34
35
  import { createExperience, createStep } from '@picked/client/plugins/create-experience'
35
36
 
36
- // Define standalone steps
37
+ // Define the steps on their own.
37
38
  const welcomeStep = createStep({
38
39
  id: 'welcome',
39
40
  name: 'Welcome',
@@ -44,7 +45,7 @@ const contactStep = createStep({
44
45
  name: 'Contact Details',
45
46
  })
46
47
 
47
- // Define the experience and add steps to build the flow
48
+ // Define the experience, then add the steps to build the flow.
48
49
  const onboardingExperience = createExperience({
49
50
  id: 'onboarding',
50
51
  name: 'User Onboarding',
@@ -55,13 +56,14 @@ onboardingExperience.addStep(welcomeStep)
55
56
  onboardingExperience.addStep(contactStep)
56
57
  ```
57
58
 
58
- Each step requires a `name` that identifies it in analytics events. An `id` is optional — one will be generated automatically if omitted.
59
+ Each step needs a `name`, and your analytics events use that name to identify the
60
+ step. An `id` is optional. Picked makes one for you when you give none.
59
61
 
60
- Steps can be used on multiple experiences.
62
+ You can use the same step on more than one experience.
61
63
 
62
- ### Adding Fields to Steps
64
+ ### Add a field to a step
63
65
 
64
- Fields are defined with `createField()`.
66
+ Define a field with `createField()`.
65
67
 
66
68
  ```ts
67
69
  import { createStep, createField } from '@picked/client/plugins/create-experience'
@@ -77,7 +79,8 @@ const emailField = createField({
77
79
  })
78
80
  ```
79
81
 
80
- Fields are added to a step with `step.addField()`, or an array of `fieldIds` when creating a step:
82
+ Add a field to a step with `step.addField()`. You can also pass an array of
83
+ `fieldIds` when you create the step:
81
84
 
82
85
  ```ts
83
86
  const contactStep = createStep({
@@ -87,7 +90,7 @@ const contactStep = createStep({
87
90
 
88
91
  contactStep.addField(emailField)
89
92
 
90
- //Or
93
+ // Or:
91
94
 
92
95
  const contactStep = createStep({
93
96
  id: 'contact',
@@ -96,9 +99,9 @@ const contactStep = createStep({
96
99
  })
97
100
  ```
98
101
 
99
- ## Using conditions in an experience
102
+ ## Use a condition in an experience
100
103
 
101
- [Conditions](../conditions) are used to define when an experience will become active for the user.
104
+ A [condition](../conditions) defines when an experience becomes active for the user.
102
105
 
103
106
  ```ts
104
107
  import { createExperience, createStep } from '@picked/client/plugins/create-experience'
@@ -125,7 +128,7 @@ const productPageFunnel = createExperience({
125
128
  })
126
129
  ```
127
130
 
128
- Navigation between steps is handled with [conditions](../conditions). Use the `create`
131
+ A [condition](../conditions) also controls the move from one step to the next.
129
132
 
130
133
  ```ts
131
134
  import { createExperience, createStep } from '@picked/client/plugins/create-experience'
@@ -4,9 +4,11 @@ layout: docs
4
4
 
5
5
  # Dynamic Experiences Plugin
6
6
 
7
- A plugin that fetches your project configuration from the Picked API at runtime, letting you manage experiences, feature flags, steps, fields, and conditions entirely from the [dashboard](/experiences).
7
+ This plugin reads the options of your project from the Picked API at runtime. You can
8
+ therefore control your experiences, feature flags, steps, fields, and conditions from
9
+ the [dashboard](/experiences) alone.
8
10
 
9
- ## Setup Plugin
11
+ ## Set the plugin up
10
12
 
11
13
  :::code-group
12
14
 
@@ -25,34 +27,58 @@ client.start()
25
27
  :::
26
28
 
27
29
  :::tip
28
- Unlike [createExperiencePlugin](./create-experience.md), you do not define experiences, steps, or fields in code. All configuration is managed from the [Picked dashboard](/experiences).
30
+ With [`createExperiencePlugin`](./create-experience.md) you define an experience, a
31
+ step, and a field in code. With this plugin you do not. You control every option from
32
+ the [Picked dashboard](/experiences).
29
33
  :::
30
34
 
31
- ## How It Works
35
+ ## How it works
32
36
 
33
- When the client starts, the plugin resolves your project configuration using a three-tier strategy:
37
+ When the client starts, the plugin resolves the options of your project. It tries
38
+ three sources, in this order:
34
39
 
35
- 1. **Preview mode** — When you [enter preview mode from the dashboard](/workspaces#preview), the plugin fetches fresh config from the API. Feature flag decisions use session storage so they are not persisted beyond the current tab.
40
+ 1. **Preview mode** — after you [enter preview mode from the dashboard](/workspaces#preview),
41
+ the plugin reads fresh options from the API. A feature flag decision then uses
42
+ session storage, so it does not stay after you close the tab.
36
43
 
37
- 2. **Build-time config** — If `projectConfig` was passed as a plugin option, or the [Vite Plugin](/vite-plugin) injected config at build time, the plugin uses that config with no network request.
44
+ 2. **Build-time options** — the plugin uses the options that it has already, and
45
+ makes no network request. This applies in two cases. You pass `projectConfig` as a
46
+ plugin option, or the [Vite Plugin](/vite-plugin) injects the options at build
47
+ time.
38
48
 
39
- 3. **Runtime fetch** — Otherwise, the plugin fetches config from the API when the client starts.
49
+ 3. **Runtime read** — in every other case, the plugin reads the options from the API
50
+ when the client starts.
40
51
 
41
- Once resolved, the plugin registers all feature flags, conditions, fields, steps, and experiences with the client. Experiences then initialize according to their [conditions](/sdk/conditions) and [target selectors](/sdk/experiences).
52
+ After the options resolve, the plugin registers every feature flag, condition, field,
53
+ step, and experience on the client. Each experience then starts, and its
54
+ [conditions](/sdk/conditions) and [target selectors](/sdk/experiences) decide
55
+ whether it does.
42
56
 
43
57
  :::note
44
- The [Vite Plugin](/vite-plugin) eliminates the runtime API request in production by embedding your project config at build time. This is the recommended setup for Vite-based applications.
58
+ The [Vite Plugin](/vite-plugin) embeds the options of your project at build time. In
59
+ production there is therefore no API request at runtime. This is the recommended
60
+ setup for an application that uses Vite.
45
61
  :::
46
62
 
47
63
  ## Feature flag allocation
48
64
 
49
- Each [feature flag](/sdk/features) in your project has an `enabled` state and a `weight` (0–100) that controls the percentage of traffic allocated to it. The plugin uses a deterministic hash of the user's session ID to produce a stable allocation value. A user whose value falls below the flag's weight is allocated to that flag — the same user always gets the same result.
65
+ Each [feature flag](/sdk/features) in your project has an `enabled` state and a
66
+ `weight` from 0 to 100. The weight controls what percentage of your traffic Picked
67
+ allocates to the flag.
50
68
 
51
- Feature flag decisions are cached in `localStorage` by default, or `sessionStorage` when in [preview mode](#preview-mode).
69
+ The plugin makes a deterministic hash from the session ID of the user, and that gives
70
+ a stable allocation value. Picked allocates a user whose value is below the weight of
71
+ the flag. The same user always gets the same result.
52
72
 
53
- ## Preview Mode
73
+ The plugin caches a feature flag decision in `localStorage` by default. In
74
+ [preview mode](#preview-mode) it uses `sessionStorage`.
54
75
 
55
- When you [enter preview mode from the dashboard](/workspaces#preview), the SDK receives a preview token and activates preview mode:
76
+ ## Preview mode
56
77
 
57
- - The plugin fetches fresh config from the API using the token, bypassing any build-time or cached config
58
- - Feature flag decisions use session storage, so closing the tab resets them
78
+ After you [enter preview mode from the dashboard](/workspaces#preview), the SDK
79
+ receives a preview token and starts preview mode:
80
+
81
+ - The plugin reads fresh options from the API with that token. It bypasses the
82
+ build-time options and the cached options.
83
+ - A feature flag decision uses session storage. The decision resets when you close
84
+ the tab.
@@ -4,11 +4,15 @@ layout: docs
4
4
 
5
5
  # Predicates
6
6
 
7
- Predicates enable reusable condition logic based on current application state. Each predicate is a pure function that receives a state snapshot containing responses, fields, browser context, and optional arguments.
7
+ A predicate holds reusable condition logic that reads the current state of your
8
+ application. Each predicate is a pure function. It takes a state snapshot, and that
9
+ snapshot holds the responses, the fields, the browser context, and optional
10
+ arguments.
8
11
 
9
- ## Defining Predicates
12
+ ## Define a predicate
10
13
 
11
- Define predicates when creating the Picked client by passing a `predicates` object. Each predicate requires a unique identifier and a function that receives a state snapshot:
14
+ Define your predicates when you create the Picked client. Pass a `predicates` object.
15
+ Each predicate needs a unique identifier and a function that takes a state snapshot:
12
16
 
13
17
  ```ts
14
18
  import { createClient } from '@picked/client'
@@ -21,24 +25,28 @@ const client = createClient({
21
25
  },
22
26
  'get-account-tier': async () => {
23
27
  const data = await fetch('/api/get-account-tier').then((r) => r.json())
24
- return data.result // returns "trial"
28
+ return data.result // Returns "trial".
25
29
  },
26
30
  },
27
31
  })
28
32
  ```
29
33
 
30
- ## How Predicates Work
34
+ ## How predicates work
31
35
 
32
- When a predicate is registered and used in conditions, the SDK follows this execution flow:
36
+ After you register a predicate and a condition uses it, the SDK follows this flow:
33
37
 
34
- 1. **Registration**: Predicates are registered to the Picked client during initialization
35
- 2. **State Snapshot Creation**: When invoked, predicates receive a frozen state snapshot containing current responses, fields, and browser context
36
- 3. **Condition Evaluation**: Experience conditions invoke predicates with a fresh snapshot whenever called
37
- 4. **Return Flexibility**: Predicates can return boolean, string, string array, undefined, or promises that resolve to these types
38
+ 1. **Registration**: you register each predicate on the Picked client at startup.
39
+ 2. **State snapshot**: the SDK gives the predicate a frozen state snapshot. The
40
+ snapshot holds the current responses, the fields, and the browser context.
41
+ 3. **Condition evaluation**: an experience condition invokes the predicate with a
42
+ fresh snapshot on each call.
43
+ 4. **Return types**: a predicate can return a boolean, a string, an array of
44
+ strings, or `undefined`. It can also return a promise that resolves to one of
45
+ those.
38
46
 
39
- ## Using Predicates in Conditions
47
+ ## Use a predicate in a condition
40
48
 
41
- Predicates are most commonly used within condition logic via the `invoke` operator:
49
+ Most predicates are used inside condition logic, through the `invoke` operator:
42
50
 
43
51
  ```ts
44
52
  const myExperience = createExperience({
@@ -49,10 +57,10 @@ const myExperience = createExperience({
49
57
  '==': [
50
58
  {
51
59
  invoke: {
52
- var: 'predicates.is-example-page', // Reference predicate that calls user endpoint
60
+ var: 'predicates.is-example-page', // Points at a predicate that calls your endpoint.
53
61
  },
54
62
  },
55
- true, // Check if return value is true
63
+ true, // Check that the return value is true.
56
64
  ],
57
65
  },
58
66
  {
@@ -62,7 +70,7 @@ const myExperience = createExperience({
62
70
  var: 'predicates.account-tier',
63
71
  },
64
72
  },
65
- '/trial', // If string matches, evaluate to true
73
+ '/trial', // The condition is true when the string matches.
66
74
  ],
67
75
  },
68
76
  ],
@@ -71,42 +79,50 @@ const myExperience = createExperience({
71
79
  ```
72
80
 
73
81
  :::tip
74
- Predicates can also be used in `validation` logic
82
+ You can also use a predicate in `validation` logic.
75
83
  :::
76
84
 
77
- ## Using the State Snapshot
85
+ ## Use the state snapshot
78
86
 
79
- Each predicate receives a `StateSnapshot` object with the following properties:
87
+ Each predicate takes a `StateSnapshot` object with these properties:
80
88
 
81
89
  ```ts
82
90
  interface StateSnapshot {
83
- client: Picked // The Picked client instance
91
+ client: Picked // The Picked client.
84
92
  responses?: Array<{
85
- // All user responses
93
+ // Every response from the user.
86
94
  fieldId: string
87
95
  value: FieldValue | undefined
88
96
  }>
89
97
  args?: unknown[]
90
- fields?: Field[] // All registered fields
91
- location?: Location // Browser location (URL, pathname, search, etc.)
92
- document?: Document // Browser document object
93
- sessionStorage?: Storage // Browser sessionStorage API
94
- localStorage?: Storage // Browser localStorage API
98
+ fields?: Field[] // Every registered field.
99
+ location?: Location // The browser location: URL, pathname, search, and more.
100
+ document?: Document // The browser document object.
101
+ sessionStorage?: Storage // The browser sessionStorage API.
102
+ localStorage?: Storage // The browser localStorage API.
95
103
  }
96
104
  ```
97
105
 
98
106
  :::warning
99
- In server-side rendering or non-browser environments, `location`, `document`, `sessionStorage`,
100
- and `localStorage` will be `undefined`.
107
+ In server-side rendering, and in any environment that is not a browser, `location`,
108
+ `document`, `sessionStorage`, and `localStorage` are `undefined`.
101
109
  :::
102
110
 
103
- ## Best Practices
104
-
105
- - **Keep predicates pure**: Predicates should be deterministic and side-effect free. Given the same snapshot, they should always return the same result
106
- - **Handle missing data gracefully**: Always check if responses or fields exist before accessing their values. Return `undefined` when data is unavailable rather than throwing errors
107
- - **Avoid heavy computations**: Predicates may be invoked frequently. Keep logic lightweight and fast
108
- - **Use async sparingly**: While async predicates are supported, they can delay experience rendering. Prefer synchronous logic when possible
109
- - **Return consistent types**: Don't mix return types (e.g., sometimes boolean, sometimes string) for the same predicate
110
- - **Use meaningful names**: Predicate IDs should clearly describe what they evaluate (e.g., `is-premium-user`, `has-items-in-cart`)
111
- - **Consider caching expensive operations**: If a predicate needs to perform expensive operations, consider caching the result in `localStorage` or `sessionStorage` within the predicate logic
112
- - **Don't mutate the snapshot**: The state snapshot is frozen. Any attempt to modify it will throw an error in strict mode
111
+ ## Best practices
112
+
113
+ - **Keep each predicate pure**: a predicate must be deterministic and free of side
114
+ effects. The same snapshot must always give the same result.
115
+ - **Handle missing data**: check that a response or a field is there before you read
116
+ its value. Return `undefined` when the data is not available. Do not throw.
117
+ - **Do not do heavy work**: the SDK can invoke a predicate many times. Keep the logic
118
+ small and fast.
119
+ - **Use an async predicate only when you must**: the SDK supports one, but it can
120
+ make the experience slow to appear. Use synchronous logic where you can.
121
+ - **Return one type**: do not return a boolean from a predicate one time and a string
122
+ the next.
123
+ - **Use a meaningful name**: a predicate ID must say what the predicate evaluates,
124
+ such as `is-premium-user` or `has-items-in-cart`.
125
+ - **Keep the result of expensive work**: when a predicate must do expensive work, put
126
+ the result in `localStorage` or `sessionStorage` inside the predicate.
127
+ - **Do not change the snapshot**: the state snapshot is frozen. A change to it throws
128
+ an error in strict mode.