@picked/client 0.0.56-next.1 → 0.0.56

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 (52) hide show
  1. package/dist/api-BfVRVGAR.cjs +1 -0
  2. package/dist/{api-CNxyQeRM.js → api-CTjJzTQT.js} +1 -1
  3. package/dist/cjs/index.js +1 -1
  4. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  5. package/dist/docs/actions.md +58 -0
  6. package/dist/docs/api/index.md +32 -0
  7. package/dist/docs/api-keys.md +33 -0
  8. package/dist/docs/conditions.md +52 -0
  9. package/dist/docs/experiences.md +33 -0
  10. package/dist/docs/features.md +51 -0
  11. package/dist/docs/fields.md +70 -0
  12. package/dist/docs/flow-builder/building-flows.md +61 -0
  13. package/dist/docs/flow-builder/condition-builder.md +54 -0
  14. package/dist/docs/flow-builder/keyboard-shortcuts.md +66 -0
  15. package/dist/docs/flow-builder/nodes.md +43 -0
  16. package/dist/docs/flow-builder/overview.md +43 -0
  17. package/dist/docs/index.md +79 -0
  18. package/dist/docs/pickedql/data-sources.md +39 -0
  19. package/dist/docs/pickedql/dimensions.md +23 -0
  20. package/dist/docs/pickedql/examples.md +165 -0
  21. package/dist/docs/pickedql/filtering.md +31 -0
  22. package/dist/docs/pickedql/index.md +34 -0
  23. package/dist/docs/pickedql/schema.md +95 -0
  24. package/dist/docs/pickedql/virtual-functions.md +84 -0
  25. package/dist/docs/projects.md +30 -0
  26. package/dist/docs/reference/index.md +11 -0
  27. package/dist/docs/reports.md +11 -0
  28. package/dist/docs/sdk/actions.md +7 -0
  29. package/dist/docs/sdk/conditions.md +131 -0
  30. package/dist/docs/sdk/content-security-policy.md +31 -0
  31. package/dist/docs/sdk/experiences.md +41 -0
  32. package/dist/docs/sdk/features.md +213 -0
  33. package/dist/docs/sdk/fields.md +263 -0
  34. package/dist/docs/sdk/index.md +74 -0
  35. package/dist/docs/sdk/plugins/create-experience.md +133 -0
  36. package/dist/docs/sdk/plugins/dynamic-experiences.md +59 -0
  37. package/dist/docs/sdk/plugins/index.md +9 -0
  38. package/dist/docs/sdk/plugins/window-object.md +20 -0
  39. package/dist/docs/sdk/predicates.md +113 -0
  40. package/dist/docs/sdk/steps.md +411 -0
  41. package/dist/docs/steps.md +35 -0
  42. package/dist/docs/team-management.md +40 -0
  43. package/dist/docs/tooling/chrome-extension.md +29 -0
  44. package/dist/docs/tooling/gtm-template.md +69 -0
  45. package/dist/docs/tooling/index.md +19 -0
  46. package/dist/docs/tooling/mcp.md +120 -0
  47. package/dist/docs/tooling/vite-plugin.md +82 -0
  48. package/dist/docs/workspaces.md +35 -0
  49. package/dist/esm/index.js +1 -1
  50. package/dist/esm/plugins/dynamic-experiences/index.js +1 -1
  51. package/package.json +2 -2
  52. package/dist/api-CtE-gT1L.cjs +0 -1
@@ -0,0 +1,11 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Reports
6
+
7
+ Reports show analytics data for your project. Track how visitors interact with your experiences and features.
8
+
9
+ ## View report data
10
+
11
+ Open a report to see its analytics. Data updates as new visitor interactions are recorded.
@@ -0,0 +1,7 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Actions
6
+
7
+ Coming Soon...
@@ -0,0 +1,131 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Conditions
6
+
7
+ Conditions evaluate of the current state of the Picked client to decide outcomes as defined by the user. Conditions are `JsonLogic`, which will always return the same result given the same input.
8
+
9
+ ## Conditions in Experiences
10
+
11
+ A common method of navigating a Funnel Experiences is using conditions between steps. The condition will evaludate values from the Picked state, and navigation the user based on a boolean value
12
+
13
+ ```ts
14
+ const questionaireFunnel = createExperience({
15
+ id: 'mini-funnel',
16
+ // experience config
17
+ })
18
+
19
+ const userInformationStep = funnel.addStep({
20
+ fields: ['email-address-field', 'sign-up-for-newsletter-field'],
21
+ })
22
+ const signUserUpToNewsletter = funnel.addStep({
23
+ // step config
24
+ })
25
+ const userDoesNotWantNewsletter = funnel.addStep({
26
+ // step config
27
+ })
28
+
29
+ const isSigningUpForNewsletter = funnel.addConditional({
30
+ condition: {
31
+ '==': [
32
+ {
33
+ var: 'responses.sign-up-for-newsletter-field', // check if response value for the field is true
34
+ },
35
+ true,
36
+ ],
37
+ },
38
+ })
39
+
40
+ userInformationStep.nextConditional(isSigningUpForNewsletter)
41
+
42
+ isSigningUpForNewsletter.addTrue(signUserUpToNewsletter)
43
+ isSigningUpForNewsletter.addFalse(userDoesNotWantNewsletter)
44
+ ```
45
+
46
+ ## Conditional Fields
47
+
48
+ Fields can be shown or hidden based on previous responses or predicates using JSON Logic conditions:
49
+
50
+ ```ts
51
+ // Show email field only if user selected "email" as a contact method
52
+ export const emailField = createField({
53
+ id: 'email-input',
54
+ name: 'email-input',
55
+ type: 'text',
56
+ label: 'Email Address',
57
+ multiple: false,
58
+ required: true,
59
+ condition: {
60
+ in: [
61
+ 'email',
62
+ {
63
+ var: 'responses.communication-methods', // Reference previous field
64
+ },
65
+ ],
66
+ },
67
+ })
68
+
69
+ // Show field based on predicate
70
+ export const mailingAddressField = createField({
71
+ id: 'mailing-address',
72
+ name: 'mailing-address',
73
+ type: 'text',
74
+ label: 'Mailing Address',
75
+ multiple: false,
76
+ required: true,
77
+ condition: {
78
+ '==': [
79
+ {
80
+ invoke: {
81
+ var: 'predicates.user-allows-mail',
82
+ },
83
+ },
84
+ true,
85
+ ],
86
+ },
87
+ })
88
+ ```
89
+
90
+ ## Field Options with Conditions
91
+
92
+ Individual options within a field can also have conditional visibility:
93
+
94
+ ```ts
95
+ export const contactMethodsField = createField({
96
+ id: 'contact-methods',
97
+ name: 'contact-methods',
98
+ type: 'text',
99
+ label: 'How should we contact you?',
100
+ multiple: true,
101
+ required: true,
102
+ options: [
103
+ {
104
+ id: 'email',
105
+ label: 'Email',
106
+ value: 'email',
107
+ },
108
+ {
109
+ id: 'phone',
110
+ label: 'Phone',
111
+ value: 'phone',
112
+ },
113
+ {
114
+ id: 'mail',
115
+ label: 'Postal Mail',
116
+ value: 'mail',
117
+ // Only show this option if user has a verified address
118
+ condition: {
119
+ '==': [
120
+ {
121
+ invoke: {
122
+ var: 'predicates.has-verified-address',
123
+ },
124
+ },
125
+ true,
126
+ ],
127
+ },
128
+ },
129
+ ],
130
+ })
131
+ ```
@@ -0,0 +1,31 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Content Security Policy
6
+
7
+ The Picked SDK makes outbound HTTPS requests to the following domains. Add them to your `connect-src` directive.
8
+
9
+ ## Required domains
10
+
11
+ | Domain | Purpose |
12
+ | --- | --- |
13
+ | `api.picked.app` | API requests — project configuration, experiences, session tracking |
14
+ | `evnts.pckd.dev` | Analytics event collection (Snowplow) |
15
+
16
+ ## Configuration
17
+
18
+ As an HTTP header:
19
+
20
+ ```
21
+ Content-Security-Policy: connect-src 'self' https://api.picked.app https://evnts.pckd.dev;
22
+ ```
23
+
24
+ Or as a `<meta>` tag:
25
+
26
+ ```html
27
+ <meta
28
+ http-equiv="Content-Security-Policy"
29
+ content="connect-src 'self' https://api.picked.app https://evnts.pckd.dev;"
30
+ />
31
+ ```
@@ -0,0 +1,41 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Experiences
6
+
7
+ Experiences enable you to conditionally show UI components in your application based on custom logic. Each experience defines a component to render, where to inject it in the DOM, and an optional condition that controls when it appears. Experiences support multi-step flows with fields and branching logic, making them ideal for onboarding, surveys, and interactive product tours
8
+
9
+ ## Creating Experiences
10
+
11
+ ```ts
12
+ import { createExperience } from '@picked/client/plugins/create-experience'
13
+
14
+ export const productPageExperience = createExperience({
15
+ id: 'product-page-experience',
16
+ name: 'Product Page Experience,
17
+ condition: {
18
+ '==': [
19
+ {
20
+ var: 'location.pathname',
21
+ },
22
+ '/product/',
23
+ ]
24
+ }
25
+ targets: [{ selector: 'body', placement: 'after' }],
26
+ component: adaptSvelteComponent(import('../ui/ProductPageExperience.svelte', {
27
+ css: [appCss],
28
+ }),
29
+ })
30
+ ```
31
+
32
+ ## How Experiences Work
33
+
34
+ When an experience is registered, the SDK follows this execution flow:
35
+
36
+ 1. **Registration**: Experiences are registered to the Picked client using [createExperiencePlugin](./plugins/create-experience.md) for code-defined experiences, or [dynamicExperiencesPlugin](./plugins/dynamic-experiences.md) for dashboard-managed experiences
37
+ 2. **Condition Evaluation**: The SDK evaluates the experience's `condition` using the logic engine to determine if it should be shown
38
+ 3. **Target Resolution**: The SDK searches the DOM for elements matching the `targets` selectors (e.g., `body`, `.product-page`, `#checkout`)
39
+ 4. **Initialization**: If the condition evaluates to `true` and at least one target element exists, the experience is initialized
40
+ 5. **Component Rendering**: The experience's component is rendered and injected into the DOM at the specified target location and placement (before, after, append, prepend, or replace)
41
+ 6. **Flow Execution**: If the experience contains steps, fields, and conditionals, users can progress through the multi-step flow with validation and branching logic
@@ -0,0 +1,213 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Features
6
+
7
+ Features enable A/B testing and feature flagging in your application. Each feature defines decision logic that resolves to a `bucket` as either a boolean value (`true`/`false`) or a string value (like `"control"`, `"variant-a"`, or `"variant-b"`). Once a feature is decided, the result is cached in browser storage to ensure consistent user experiences across sessions.
8
+
9
+ ## Defining Features
10
+
11
+ Define features when creating the Picked client by passing a `features` object. Each feature requires a unique identifier, a versioning key, a human-readable name, and a `decide` function:
12
+
13
+ ```ts
14
+ import { createClient } from '@picked/client'
15
+
16
+ const client = createClient({
17
+ organizationId: 'your-org-id',
18
+ projectId: 'your-project-id',
19
+ features: {
20
+ 'summer-sale': {
21
+ key: 'summer-sale-v1', // Version key - change to invalidate cache
22
+ name: 'Summer Sale Experiment', // Human-readable name for analytics
23
+ decide: async () => {
24
+ // Your decision logic here
25
+ const isEligible = await checkUserEligibility()
26
+ return isEligible // Returns boolean
27
+ },
28
+ storage: 'local', // Optional: 'local' (default) or 'session'
29
+ },
30
+ 'homepage-variant': {
31
+ key: 'homepage-test-v2',
32
+ name: 'Homepage A/B Test',
33
+ decide: async () => {
34
+ // Return string for multi-variant tests
35
+ return Math.random() < 0.5 ? 'variant-a' : 'variant-b'
36
+ },
37
+ },
38
+ },
39
+ })
40
+ ```
41
+
42
+ Features can also be defined as factory functions for lazy initialization:
43
+
44
+ ```ts
45
+ features: {
46
+ 'my-feature': () => ({
47
+ key: 'my-feature-v1',
48
+ name: 'My Feature',
49
+ decide: async () => true,
50
+ }),
51
+ }
52
+ ```
53
+
54
+ ## How Features Work
55
+
56
+ When a feature is registered, the SDK follows this execution order:
57
+
58
+ 1. **Registration**: Features are registered to the Picked client during initialization
59
+ 2. **Cache Check**: The SDK checks browser storage (localStorage or sessionStorage) for a previously resolved value using the feature `id` and `key`
60
+ 3. **Lazy Evaluation**: If no cached value exists, the `decide` function will be called the first time the feature is accessed
61
+ 4. **Storage**: Once resolved, the bucket value is persisted to browser storage for consistency across page loads
62
+ 5. **Event Emission**: A `feature_decide` event is fired on the event bus when a feature is resolved for the first time
63
+
64
+ ## URL-Based Overrides
65
+
66
+ You can override feature decisions via URL search parameters for testing purposes:
67
+
68
+ ```
69
+ https://example.com?picked-feature=my-feature:true
70
+ ```
71
+
72
+ Multiple overrides can be specified:
73
+
74
+ ```
75
+ https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
76
+ ```
77
+
78
+ The format is `?picked-feature=<feature-id>:<bucket-value>`.
79
+
80
+ This is useful for QA testing specific feature variants without modifying code. The override is persisted in session storage and will be cleared when the browser tab is closed.
81
+
82
+ ## Accessing Features
83
+
84
+ Once features are defined, you can access their resolved bucket values in your application code. The SDK provides framework-specific hooks and a framework-agnostic event bus for subscribing to feature decisions.
85
+
86
+ :::code-group
87
+
88
+ ```ts [Vanilla JS]
89
+ import { createClient } from '@picked/client'
90
+
91
+ const client = createClient({
92
+ /* Create client options here */
93
+ })
94
+
95
+ client.eventBus.subscribe('feature_decide', ({ feature }) => {
96
+ if (feature.id === 'my-feature') {
97
+ console.log(feature) // { id: 'my-feature', bucket: 'control' }
98
+ }
99
+ })
100
+ ```
101
+
102
+ ```ts [Svelte]
103
+ <script lang="ts">
104
+ import { getFeature } from '@picked/svelte/hooks'
105
+ // Returns an Effector store containing the feature resolver
106
+ const myFeature = getFeature('my-feature')
107
+ </script>
108
+ <!-- $myFeature is a reactive Effector store (see Effector docs for details) -->
109
+ {#if $myFeature()}
110
+ <p>My feature is enabled</p>
111
+ {:else}
112
+ <p>My feature is disabled</p>
113
+ {/if}
114
+ ```
115
+
116
+ ```ts [React]
117
+ import { useFeature } from '@picked/react/hooks'
118
+
119
+ function MyComponent() {
120
+ // Returns the resolved bucket value for 'my-feature'
121
+ const myFeature = useFeature('my-feature')
122
+
123
+ return (
124
+ <div>
125
+ {myFeature === 'control' && <p>Control variant</p>}
126
+ {myFeature === 'variant-a' && <p>Variant A</p>}
127
+ </div>
128
+ )
129
+ }
130
+
131
+ ```
132
+
133
+ :::
134
+
135
+ ## Using Features in Experience Conditions
136
+
137
+ Features can be used with experience conditions via JSON logic:
138
+
139
+ ```ts
140
+ const myExperience = createExperience({
141
+ // ... other config
142
+ condition: {
143
+ and: [
144
+ {
145
+ '==': [
146
+ {
147
+ invoke: {
148
+ var: 'features.summer-sale', // Reference feature by ID
149
+ },
150
+ },
151
+ true, // Check if bucket equals true
152
+ ],
153
+ },
154
+ ],
155
+ },
156
+ })
157
+ ```
158
+
159
+ ## Listening to Feature Decisions
160
+
161
+ Subscribe to the `feature_decide` event to track when features are resolved:
162
+
163
+ ```ts
164
+ client.eventBus.subscribe('feature_decide', ({ feature }) => {
165
+ console.log(`Feature "${feature.id}" resolved to bucket:`, feature.bucket)
166
+ // Example output: Feature "summer-sale" resolved to bucket: true
167
+
168
+ // Send to analytics
169
+ analytics.track('Feature Decision', {
170
+ featureId: feature.id,
171
+ bucket: feature.bucket,
172
+ })
173
+ })
174
+ ```
175
+
176
+ ## Versioning and cache invalidation
177
+
178
+ Feature decisions are cached in browser storage (localStorage or sessionStorage) using a key derived from the feature ID and version. This ensures visitors see a consistent experience across page loads.
179
+
180
+ ### How versioning works
181
+
182
+ When you create a feature in the dashboard, it starts at version 1. The SDK uses the version to build the cache key — for example, a feature at version 3 uses the key `v3`. This version is also included in the allocation hash, so it directly affects which variant a visitor is assigned to.
183
+
184
+ ### What happens when the version is incremented
185
+
186
+ When you increment a feature's version in the dashboard:
187
+
188
+ 1. **Cache keys change.** The old cache entry (e.g., `v3`) no longer matches the new key (`v4`), so the SDK treats the feature as undecided.
189
+ 2. **All visitors are re-allocated.** The allocation hash includes the version, so every visitor gets a new hash value and may be assigned to a different variant.
190
+ 3. **The `decide` function runs again.** On the next page load, the SDK calls `decide()` fresh and caches the new result.
191
+
192
+ > **Warning:** Incrementing the version re-allocates all visitors, not just new ones. This is irreversible. Only increment when you intentionally want to reset all assignments — for example, after changing traffic allocation weights.
193
+
194
+ ### SDK `key` vs dashboard version
195
+
196
+ If you define features statically in the SDK (not loaded from the dashboard), use the `key` field for manual versioning:
197
+
198
+ ```ts
199
+ features: {
200
+ 'summer-sale': {
201
+ key: 'summer-sale-v2', // Change to invalidate cache
202
+ // ...
203
+ },
204
+ }
205
+ ```
206
+
207
+ For features managed through the dashboard, versioning is handled automatically — the dashboard appends the version number to the cache key used by the SDK.
208
+
209
+ ## Best Practices
210
+
211
+ - **Use `session` storage for temporary tests**: Set `storage: 'session'` for short-lived experiments that shouldn't persist across browser sessions
212
+ - **Keep `decide` functions fast**: The function blocks experience rendering, so avoid heavy computations or slow network calls
213
+ - **Return consistent types**: Don't mix boolean and string returns for the same feature - pick one type and stick with it
@@ -0,0 +1,263 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Fields
6
+
7
+ Fields are used to store data for a Picked instance. Fields can be added to steps or forms and rendered as inputs, or have their values directly set.
8
+
9
+ ## Defining Fields
10
+
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
+
13
+ ```ts
14
+ import { createField } from '@picked/client/plugins/create-experience'
15
+
16
+ // Required text input
17
+ export const emailField = createField({
18
+ id: 'email-address',
19
+ name: 'email-address',
20
+ type: 'text',
21
+ label: 'Email Address',
22
+ multiple: false,
23
+ required: true,
24
+ placeholder: 'you@example.com',
25
+ })
26
+
27
+ // Multi-select dropdown with static options
28
+ export const communicationMethods = createField({
29
+ id: 'communication-methods',
30
+ name: 'communication-methods',
31
+ type: 'text',
32
+ label: 'Preferred contact methods',
33
+ multiple: true,
34
+ required: true,
35
+ options: [
36
+ {
37
+ id: 'email',
38
+ label: 'Email',
39
+ value: 'email',
40
+ },
41
+ {
42
+ id: 'phone',
43
+ label: 'Phone',
44
+ value: 'phone',
45
+ },
46
+ {
47
+ id: 'sms',
48
+ label: 'SMS',
49
+ value: 'sms',
50
+ },
51
+ ],
52
+ })
53
+ ```
54
+
55
+ ## How Fields Work
56
+
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' }`
65
+
66
+ ## Field Types
67
+
68
+ Fields support four primitive types, `text`, `number`, `boolean` and `object`:
69
+
70
+ ```ts
71
+ // Single text input
72
+ const nameField = createField({
73
+ type: 'text',
74
+ multiple: false,
75
+ // ...
76
+ })
77
+ ```
78
+
79
+ Provide an `options` array to allow multiple select:
80
+
81
+ ```ts
82
+ const nameField = createField({
83
+ type: 'text',
84
+ multiple: true,
85
+ options: [
86
+ {
87
+ id: 'option-one',
88
+ label: 'Option One',
89
+ value: 'product-id-one',
90
+ },
91
+ {
92
+ id: 'option-two',
93
+ label: 'Option Two',
94
+ value: 'product-id-two',
95
+ },
96
+ {
97
+ id: 'option-three',
98
+ label: 'Option Three',
99
+ value: 'product-id-three',
100
+ },
101
+ ],
102
+ })
103
+ ```
104
+
105
+ ## Conditions
106
+
107
+ Fields and field options are compatible with condition logic:
108
+
109
+ ```ts
110
+ const conditionalField = createField({
111
+ id: 'conditional-field',
112
+ type: 'text',
113
+ condition: {
114
+ invoke: {
115
+ var: 'responses.has-answered-question',
116
+ },
117
+ },
118
+ options: [
119
+ {
120
+ id: 'conditional-option',
121
+ label: 'Conditional Label',
122
+ condition: {
123
+ invoke: {
124
+ var: 'responses.eligible-for-option',
125
+ },
126
+ },
127
+ },
128
+ ],
129
+ })
130
+ ```
131
+
132
+ ## Field Validation
133
+
134
+ Validate user input using JSON Logic rules. The `$input` variable contains the current field value:
135
+
136
+ ```ts
137
+ const quantityField = createField({
138
+ id: 'quantity',
139
+ name: 'quantity',
140
+ type: 'number',
141
+ label: 'Quantity',
142
+ multiple: false,
143
+ required: true,
144
+ validation: {
145
+ catch: {
146
+ or: [
147
+ {
148
+ and: [
149
+ {
150
+ // Input value is above 0
151
+ '>': [
152
+ {
153
+ var: '$input',
154
+ },
155
+ 0,
156
+ ],
157
+ },
158
+ {
159
+ // Input value is equal or below 999
160
+ '<=': [
161
+ {
162
+ var: '$input',
163
+ },
164
+ 999,
165
+ ],
166
+ },
167
+ ],
168
+ },
169
+ {
170
+ throw: 'Please enter a value between 1 and 999',
171
+ },
172
+ ],
173
+ },
174
+ },
175
+ })
176
+ ```
177
+
178
+ :::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.
180
+ :::
181
+
182
+ ## Field Policies
183
+
184
+ Control how field data is tracked and stored using privacy policies:
185
+
186
+ ```ts
187
+ const sensitiveField = createField({
188
+ id: 'ssn',
189
+ name: 'ssn',
190
+ type: 'text',
191
+ label: 'Social Security Number',
192
+ multiple: false,
193
+ required: true,
194
+ policies: {
195
+ doNotTrackValue: true, // Don't send value to analytics
196
+ doNotStoreValue: true, // Don't persist value to backend
197
+ },
198
+ })
199
+ ```
200
+
201
+ **Privacy policies**:
202
+
203
+ - `doNotTrackValue`: Excludes field value from analytics events (only field ID is tracked)
204
+ - `doNotStoreValue`: Prevents field value from being persisted to backend storage
205
+
206
+ Use these policies for sensitive data like SSNs, credit cards, or passwords that should only exist client-side.
207
+
208
+ ## Field Metadata
209
+
210
+ Enhance user experience with additional descriptive text:
211
+
212
+ ```ts
213
+ const passwordField = createField({
214
+ id: 'password',
215
+ name: 'password',
216
+ type: 'text',
217
+ label: 'Password',
218
+ multiple: false,
219
+ required: true,
220
+ placeholder: 'Enter a strong password',
221
+ description: 'Your password must be at least 8 characters',
222
+ helpText: 'Use a mix of letters, numbers, and symbols for better security',
223
+ })
224
+ ```
225
+
226
+ **Metadata fields**:
227
+
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
231
+
232
+ ## Accessing Field Values
233
+
234
+ Field responses are stored in the experience state and can be accessed via the `responses` object:
235
+
236
+ :::code-group
237
+
238
+ ```ts [In Conditions]
239
+ // Reference in subsequent field conditions
240
+ const sensitiveField = createField({
241
+ condition: {
242
+ '==': [
243
+ {
244
+ var: 'responses.field-name',
245
+ },
246
+ 'expected-value',
247
+ ],
248
+ },
249
+ })
250
+ ```
251
+
252
+ :::
253
+
254
+ ## Best Practices
255
+
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