picked 0.0.101-next.2 → 0.0.101

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 (175) hide show
  1. package/README.md +27 -12
  2. package/docs/explanation/conditions-and-the-logic-engine.md +94 -0
  3. package/docs/explanation/content-references.md +99 -0
  4. package/docs/explanation/feature-flag-allocation.md +218 -0
  5. package/docs/explanation/how-picked-works.md +124 -0
  6. package/docs/explanation/how-pickedql-works.md +76 -0
  7. package/docs/explanation/pages-and-flows.md +64 -0
  8. package/docs/explanation/the-content-model.md +87 -0
  9. package/docs/explanation/the-two-apis.md +68 -0
  10. package/docs/explanation/why-server-side-rendering.md +76 -0
  11. package/docs/explanation/workspaces-and-versions.md +71 -0
  12. package/docs/how-to/add-a-step-field-or-action.md +56 -0
  13. package/docs/how-to/analytics/index.md +324 -0
  14. package/docs/how-to/archive-or-delete-a-workspace.md +59 -0
  15. package/docs/how-to/authenticate-the-cli.md +101 -0
  16. package/docs/how-to/authenticate-with-a-magic-link.md +56 -0
  17. package/docs/how-to/authenticate-with-a-password.md +68 -0
  18. package/docs/how-to/authenticate-with-an-api-key.md +60 -0
  19. package/docs/how-to/authenticate-with-oauth.md +49 -0
  20. package/docs/how-to/build-a-flow.md +106 -0
  21. package/docs/how-to/call-an-external-api-from-a-workflow.md +116 -0
  22. package/docs/how-to/connect-the-mcp-server.md +46 -0
  23. package/docs/how-to/create-a-content-entry.md +58 -0
  24. package/docs/how-to/create-a-project.md +40 -0
  25. package/docs/how-to/create-an-experience.md +51 -0
  26. package/docs/how-to/create-and-revoke-an-api-key.md +54 -0
  27. package/docs/how-to/debug-with-the-chrome-extension.md +57 -0
  28. package/docs/how-to/define-a-page-layout.md +54 -0
  29. package/docs/how-to/fill-and-publish-a-page.md +82 -0
  30. package/docs/how-to/install-the-gtm-template.md +84 -0
  31. package/docs/how-to/install-the-sdk.md +93 -0
  32. package/docs/how-to/install-the-vite-plugin.md +69 -0
  33. package/docs/how-to/invite-and-remove-members.md +45 -0
  34. package/docs/how-to/point-one-entry-at-another.md +75 -0
  35. package/docs/how-to/preview-a-workspace.md +39 -0
  36. package/docs/how-to/publish-a-workspace.md +42 -0
  37. package/docs/how-to/read-a-feature-flag.md +113 -0
  38. package/docs/how-to/read-a-referenced-entry.md +109 -0
  39. package/docs/how-to/register-a-component.md +55 -0
  40. package/docs/how-to/register-an-action-handler.md +85 -0
  41. package/docs/how-to/register-an-api-that-uses-oauth2.md +107 -0
  42. package/docs/how-to/render-a-step-form.md +131 -0
  43. package/docs/how-to/render-an-unpublished-preview.md +41 -0
  44. package/docs/how-to/retry-a-failed-call-with-a-loop.md +81 -0
  45. package/docs/how-to/reuse-a-section.md +64 -0
  46. package/docs/how-to/reuse-a-step-field-or-action.md +39 -0
  47. package/docs/how-to/roll-back-a-published-version.md +39 -0
  48. package/docs/how-to/run-an-ab-test.md +59 -0
  49. package/docs/how-to/run-code-on-a-form-submission.md +125 -0
  50. package/docs/how-to/server-render-a-page.md +81 -0
  51. package/docs/how-to/set-a-content-security-policy.md +32 -0
  52. package/docs/how-to/set-up-mfa.md +126 -0
  53. package/docs/how-to/switch-workspaces.md +122 -0
  54. package/docs/how-to/upload-and-use-an-asset.mdx +87 -0
  55. package/docs/how-to/validate-a-field.md +52 -0
  56. package/docs/how-to/wait-for-an-action-response.md +182 -0
  57. package/docs/how-to/write-a-condition.md +57 -0
  58. package/docs/how-to/write-a-predicate.md +60 -0
  59. package/docs/index.md +59 -0
  60. package/docs/reference/dashboard/actions.mdx +65 -0
  61. package/docs/reference/dashboard/api-keys.mdx +47 -0
  62. package/docs/reference/dashboard/conditions.mdx +51 -0
  63. package/docs/reference/dashboard/content/asset-library.mdx +74 -0
  64. package/docs/reference/dashboard/content/blocks.mdx +66 -0
  65. package/docs/reference/dashboard/content/content-entries.mdx +109 -0
  66. package/docs/reference/dashboard/content/models.mdx +119 -0
  67. package/docs/reference/dashboard/content/pages.mdx +72 -0
  68. package/docs/reference/dashboard/experiences.mdx +61 -0
  69. package/docs/reference/dashboard/external-apis.md +182 -0
  70. package/docs/reference/dashboard/features.mdx +165 -0
  71. package/docs/reference/dashboard/fields.mdx +120 -0
  72. package/docs/reference/dashboard/flow-builder/canvas.mdx +58 -0
  73. package/docs/reference/dashboard/flow-builder/condition-builder.md +69 -0
  74. package/docs/reference/dashboard/flow-builder/keyboard-shortcuts.md +72 -0
  75. package/docs/reference/dashboard/flow-builder/nodes.md +74 -0
  76. package/docs/reference/dashboard/labels.md +321 -0
  77. package/docs/reference/dashboard/lead-search.md +235 -0
  78. package/docs/reference/dashboard/projects.mdx +53 -0
  79. package/docs/reference/dashboard/reports.mdx +53 -0
  80. package/docs/reference/dashboard/steps.mdx +47 -0
  81. package/docs/reference/dashboard/team-management.md +38 -0
  82. package/docs/reference/dashboard/workflow-runs.md +157 -0
  83. package/docs/reference/dashboard/workflow-templates.md +182 -0
  84. package/docs/reference/dashboard/workflow-test-runs.md +85 -0
  85. package/docs/reference/dashboard/workspace-versioning.mdx +42 -0
  86. package/docs/reference/dashboard/workspaces.mdx +91 -0
  87. package/docs/reference/delivery-api/experience-content.md +152 -0
  88. package/docs/reference/delivery-api/index.md +45 -0
  89. package/docs/reference/delivery-api/preview-caching-crawling.md +79 -0
  90. package/docs/reference/delivery-api/project-config.md +65 -0
  91. package/docs/reference/delivery-api/workspace-schema.md +107 -0
  92. package/docs/reference/glossary.md +201 -0
  93. package/docs/reference/management-api/authentication.md +87 -0
  94. package/docs/reference/management-api/authorization-and-scopes.md +291 -0
  95. package/docs/reference/management-api/error-handling.md +232 -0
  96. package/docs/reference/management-api/pagination.md +192 -0
  97. package/docs/reference/management-api/schema/action.md +261 -0
  98. package/docs/reference/management-api/schema/analytics-session.md +363 -0
  99. package/docs/reference/management-api/schema/analytics.md +73 -0
  100. package/docs/reference/management-api/schema/api-key.md +170 -0
  101. package/docs/reference/management-api/schema/auth.md +1177 -0
  102. package/docs/reference/management-api/schema/cms-fields.md +954 -0
  103. package/docs/reference/management-api/schema/cms-models.md +1155 -0
  104. package/docs/reference/management-api/schema/common.md +362 -0
  105. package/docs/reference/management-api/schema/condition.md +254 -0
  106. package/docs/reference/management-api/schema/design-system.md +199 -0
  107. package/docs/reference/management-api/schema/experience.md +1098 -0
  108. package/docs/reference/management-api/schema/external-api.md +637 -0
  109. package/docs/reference/management-api/schema/feature.md +350 -0
  110. package/docs/reference/management-api/schema/feedback.md +103 -0
  111. package/docs/reference/management-api/schema/field.md +400 -0
  112. package/docs/reference/management-api/schema/index.md +243 -0
  113. package/docs/reference/management-api/schema/integration.md +272 -0
  114. package/docs/reference/management-api/schema/label.md +349 -0
  115. package/docs/reference/management-api/schema/lead.md +340 -0
  116. package/docs/reference/management-api/schema/mcp-server.md +299 -0
  117. package/docs/reference/management-api/schema/metric.md +223 -0
  118. package/docs/reference/management-api/schema/operation.md +96 -0
  119. package/docs/reference/management-api/schema/organization.md +749 -0
  120. package/docs/reference/management-api/schema/project.md +607 -0
  121. package/docs/reference/management-api/schema/report.md +243 -0
  122. package/docs/reference/management-api/schema/saved-view.md +288 -0
  123. package/docs/reference/management-api/schema/shopify.md +795 -0
  124. package/docs/reference/management-api/schema/step.md +273 -0
  125. package/docs/reference/management-api/schema/tenant-branding.md +185 -0
  126. package/docs/reference/management-api/schema/workbench.md +246 -0
  127. package/docs/reference/management-api/schema/workflow-run.md +263 -0
  128. package/docs/reference/management-api/schema/workflow.md +1110 -0
  129. package/docs/reference/management-api/schema/workspace-version.md +823 -0
  130. package/docs/reference/management-api/schema/workspace.md +917 -0
  131. package/docs/reference/pickedql/data-sources.md +174 -0
  132. package/docs/reference/pickedql/dimensions.md +103 -0
  133. package/docs/reference/pickedql/filtering.md +64 -0
  134. package/docs/reference/pickedql/index.md +18 -0
  135. package/docs/reference/pickedql/schema.md +172 -0
  136. package/docs/reference/pickedql/virtual-functions.md +248 -0
  137. package/docs/reference/sdk/actions.md +154 -0
  138. package/docs/reference/sdk/analytics-events.md +453 -0
  139. package/docs/reference/sdk/conditions.md +147 -0
  140. package/docs/reference/sdk/content-security-policy.md +21 -0
  141. package/docs/reference/sdk/content.md +253 -0
  142. package/docs/reference/sdk/experiences.md +51 -0
  143. package/docs/reference/sdk/features.md +256 -0
  144. package/docs/reference/sdk/fields.md +255 -0
  145. package/docs/reference/sdk/index.md +68 -0
  146. package/docs/reference/sdk/plugins/create-experience.md +123 -0
  147. package/docs/reference/sdk/plugins/dynamic-experiences.md +74 -0
  148. package/docs/reference/sdk/plugins/fingerprintjs.md +39 -0
  149. package/docs/reference/sdk/plugins/hotjar.md +60 -0
  150. package/docs/reference/sdk/plugins/index.md +33 -0
  151. package/docs/reference/sdk/plugins/sentry.md +51 -0
  152. package/docs/reference/sdk/plugins/shopify.md +109 -0
  153. package/docs/reference/sdk/plugins/window-object.md +25 -0
  154. package/docs/reference/sdk/predicates.md +43 -0
  155. package/docs/reference/sdk/react.md +61 -0
  156. package/docs/reference/sdk/ssr/index.md +33 -0
  157. package/docs/reference/sdk/ssr/rendering-and-hydration.md +103 -0
  158. package/docs/reference/sdk/ssr/serialize.md +80 -0
  159. package/docs/reference/sdk/ssr/slots-and-placements.md +113 -0
  160. package/docs/reference/sdk/steps.md +86 -0
  161. package/docs/reference/sdk/svelte.md +74 -0
  162. package/docs/reference/sdk/type-generation.md +265 -0
  163. package/docs/reference/tools/admin-api-client.md +89 -0
  164. package/docs/reference/tools/chrome-extension.md +16 -0
  165. package/docs/reference/tools/cli.md +319 -0
  166. package/docs/reference/tools/delivery-api-client.md +60 -0
  167. package/docs/reference/tools/image-optimisation-service.md +104 -0
  168. package/docs/reference/tools/mcp-run.md +121 -0
  169. package/docs/reference/tools/mcp.md +391 -0
  170. package/docs/reference/tools/vite-plugin.md +163 -0
  171. package/docs/tutorials/first-api-call.md +253 -0
  172. package/docs/tutorials/first-experience-in-the-dashboard.mdx +160 -0
  173. package/docs/tutorials/first-experience.mdx +254 -0
  174. package/docs/tutorials/first-server-rendered-page.mdx +186 -0
  175. package/package.json +8 -7
@@ -0,0 +1,109 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to read a referenced entry
7
+
8
+ This guide shows you how to read the content that a
9
+ [Reference field](/reference/dashboard/content/models#reference-fields) points at. You
10
+ need a component that renders a step first. See
11
+ [How to render a step form](/how-to/render-a-step-form).
12
+
13
+ A Reference field holds a pointer, and not the content. The entry travels beside the
14
+ content of the step, so you resolve the pointer against the content of the client.
15
+
16
+ ## 1. Resolve a single reference
17
+
18
+ Pass the raw field value to `useReference()` in React, or to `getReference()` in Svelte.
19
+
20
+ :::code-group
21
+
22
+ ```tsx [React]
23
+ import { useReference, useStepContent, useStoreGlobal } from '@picked/react/hooks'
24
+
25
+ export function ProductCategory() {
26
+ const content = useStepContent()
27
+ const category = useStoreGlobal(useReference(content?.category))
28
+
29
+ if (!category) return null
30
+
31
+ return <span>{String(category.data.name ?? '')}</span>
32
+ }
33
+ ```
34
+
35
+ ```svelte [Svelte]
36
+ <script lang="ts">
37
+ import { getStepContent, getReference } from '@picked/svelte/hooks'
38
+
39
+ const content = getStepContent()
40
+ const category = getReference(content.map((c) => c?.category ?? null, { skipVoid: false }))
41
+ </script>
42
+
43
+ {#if $category}
44
+ <span>{$category.data.name ?? ''}</span>
45
+ {/if}
46
+ ```
47
+
48
+ :::
49
+
50
+ In Svelte, pass a store and not a plain value. Content arrives after the component
51
+ starts. A plain value therefore holds the result at "not set".
52
+
53
+ ## 2. Resolve a repeatable reference
54
+
55
+ Use `useReferences()` or `getReferences()`. They give a list in field order.
56
+
57
+ :::code-group
58
+
59
+ ```tsx [React]
60
+ import { useReferences, useStepContent, useStoreGlobal } from '@picked/react/hooks'
61
+
62
+ export function ProductCategories() {
63
+ const content = useStepContent()
64
+ const categories = useStoreGlobal(useReferences(content?.categories))
65
+
66
+ return (
67
+ <ul>
68
+ {categories.map((category) => (
69
+ <li key={category.id}>{String(category.data.name ?? '')}</li>
70
+ ))}
71
+ </ul>
72
+ )
73
+ }
74
+ ```
75
+
76
+ ```svelte [Svelte]
77
+ <script lang="ts">
78
+ import { getStepContent, getReferences } from '@picked/svelte/hooks'
79
+
80
+ const content = getStepContent()
81
+ const categories = getReferences(content.map((c) => c?.categories ?? null, { skipVoid: false }))
82
+ </script>
83
+
84
+ <ul>
85
+ {#each $categories as category (category.id)}
86
+ <li>{category.data.name ?? ''}</li>
87
+ {/each}
88
+ </ul>
89
+ ```
90
+
91
+ :::
92
+
93
+ The list holds no empty places, so you need no check for each item.
94
+
95
+ ## Handle a target that does not resolve
96
+
97
+ A single reference gives an empty result when Picked cannot resolve the target. A
98
+ repeatable reference leaves the item out of the list.
99
+
100
+ This happens when somebody deleted the target, when the target belongs to another
101
+ workspace, or when the chain of references is deeper than the limit.
102
+
103
+ Render something else for the empty case, as the React example above does.
104
+
105
+ ## Related
106
+
107
+ - [Content](/reference/sdk/content) — every hook and its return type.
108
+ - [About content references](/explanation/content-references) — why the entry travels separately.
109
+ - [How to point one entry at another](/how-to/point-one-entry-at-another) — the dashboard side.
@@ -0,0 +1,55 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to register a component
7
+
8
+ This guide shows you how to connect a component in your application to an experience in
9
+ the dashboard.
10
+
11
+ ## Steps
12
+
13
+ 1. Wrap your component with the adapter for your framework.
14
+ 2. Pass it in the `components` object of `createClient()`.
15
+ 3. Use the same key as the component name of the experience.
16
+
17
+ :::code-group
18
+
19
+ ```tsx [React]
20
+ import { createClient } from 'picked'
21
+ import { adaptReactComponent } from '@picked/react'
22
+ import { MyWidget } from './MyWidget'
23
+
24
+ const picked = createClient({
25
+ projectId: 'your-project-id',
26
+ components: {
27
+ MyWidget: adaptReactComponent(MyWidget),
28
+ },
29
+ })
30
+ ```
31
+
32
+ ```ts [Svelte]
33
+ import { createClient } from 'picked'
34
+ import { adaptSvelteComponent } from '@picked/svelte'
35
+ import MyWidget from './MyWidget.svelte'
36
+
37
+ const picked = createClient({
38
+ projectId: 'your-project-id',
39
+ components: {
40
+ MyWidget: adaptSvelteComponent(MyWidget),
41
+ },
42
+ })
43
+ ```
44
+
45
+ :::
46
+
47
+ :::warning
48
+ `components` takes an adapted component. A plain React or Svelte component does not work
49
+ without `adaptReactComponent` or `adaptSvelteComponent`.
50
+ :::
51
+
52
+ ## Related
53
+
54
+ - [SDK experiences](/reference/sdk/experiences) — the experience shape.
55
+ - [How to install the SDK](/how-to/install-the-sdk).
@@ -0,0 +1,85 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to register an action handler
7
+
8
+ This guide shows you how to connect an action node in a flow to a function in your
9
+ application. You need an action in your project first, with a key. See
10
+ [Actions](/reference/dashboard/actions).
11
+
12
+ A published action that starts a workflow runs without a handler. A handler under its key
13
+ replaces the workflow.
14
+
15
+ ## 1. Register the handler
16
+
17
+ Pass an `actions` object to `createClient()`. Each key must match an action key from the
18
+ dashboard:
19
+
20
+ ```ts
21
+ import { createClient } from 'picked'
22
+
23
+ const picked = createClient({
24
+ projectId: 'your-project-id',
25
+ actions: {
26
+ 'track-signup': () => {
27
+ analytics.track('signup_completed')
28
+ },
29
+ notify: (params) => {
30
+ sendNotification(params)
31
+ },
32
+ },
33
+ })
34
+ ```
35
+
36
+ The SDK calls the function that matches when the flow reaches the action node.
37
+
38
+ ## 2. Throw only for a true error
39
+
40
+ The flow takes the failure branch when your handler throws or its promise rejects. Throw
41
+ when the request itself did not complete:
42
+
43
+ ```ts
44
+ 'check-eligibility': async () => {
45
+ const response = await fetch('/api/eligibility')
46
+ // Throw only when the request itself fails. That takes the failure branch.
47
+ if (!response.ok) throw new Error('Eligibility request failed')
48
+ }
49
+ ```
50
+
51
+ ## 3. Return a response
52
+
53
+ The value that your handler returns is the response of the action. Code that runs the
54
+ action reads this value:
55
+
56
+ ```ts
57
+ 'quote-premium': async ({ responses }) => {
58
+ const age = responses.find((r) => r.fieldKey === 'age')?.value
59
+ const { premium } = await fetch(`/api/premium?age=${age}`).then((r) => r.json())
60
+ return { premium }
61
+ }
62
+ ```
63
+
64
+ ## 4. Record a negative answer as a response
65
+
66
+ A valid negative result is not a failure. An endpoint can answer `200` with a "no". Store
67
+ that value as a response and branch on it with a condition instead:
68
+
69
+ ```ts
70
+ 'check-serviced-area': async ({ client, responses }) => {
71
+ const postcode = responses.find((r) => r.fieldKey === 'postcode')?.value
72
+ const { serviced } = await fetch(`/api/serviced?postcode=${postcode}`).then((r) => r.json())
73
+ // Store the outcome as a response. A later condition then takes the branch.
74
+ client.responses.register('is-serviced', serviced)
75
+ }
76
+ ```
77
+
78
+ ## Related
79
+
80
+ - [About how Picked works](/explanation/how-picked-works#how-an-action-decides-which-way-to-go)
81
+ — why a returned `false` is a success.
82
+ - [SDK actions](/reference/sdk/actions) — what the handler receives and returns.
83
+ - [How to wait for the response of an action](/how-to/wait-for-an-action-response) — show the
84
+ response in a component.
85
+ - [Actions](/reference/dashboard/actions) — the dashboard options.
@@ -0,0 +1,107 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to register an API that uses OAuth2
7
+
8
+ This guide shows you how to let a workflow call an API whose access tokens expire, such as
9
+ Zoho CRM. You need a client ID and a client secret from the provider. For the refresh token
10
+ grant, you also need a refresh token.
11
+
12
+ Picked keeps the credential, gets an access token from the provider, and sends the token
13
+ with each request. Picked gets a new token before the old token expires.
14
+
15
+ ## 1. Make a refresh token in Zoho
16
+
17
+ Do this step for Zoho CRM only. For another provider, get the credential from the
18
+ provider's documentation.
19
+
20
+ 1. Open the Zoho API console for the region of your Zoho account.
21
+ 2. Add a client of the type `Self Client`.
22
+ 3. Make a grant code with the scopes that the workflow needs, such as
23
+ `ZohoCRM.modules.leads.CREATE`.
24
+ 4. Send a `POST` request to the token URL of your region, such as
25
+ `https://accounts.zoho.com.au/oauth/v2/token`. Put the parameters in the form body, not
26
+ in the URL. The parameters are `grant_type=authorization_code`, `client_id`,
27
+ `client_secret`, and `code` with the grant code. The grant code expires soon, so do this
28
+ step immediately.
29
+ 5. Keep the `refresh_token` from the answer.
30
+
31
+ :::warning
32
+ Make the refresh token as a Zoho user that only Picked uses. Zoho keeps 20 refresh tokens
33
+ for each user and client. When you make one more, Zoho stops the oldest refresh token. That
34
+ token can be the token that Picked uses.
35
+ :::
36
+
37
+ ## 2. Register the external API
38
+
39
+ 1. Open **Integrations**.
40
+ 2. Choose **Configure external APIs**.
41
+ 3. Choose **New external API**.
42
+ 4. For Zoho CRM, set **Preset** to the region of your Zoho account. The preset sets the
43
+ **Display name**, the **Origin**, the **Credential** and the settings of the OAuth2
44
+ credential. Then go to step 10. If **Preset** does not show your region, do steps 5 to 9.
45
+ 5. Enter a **Display name**.
46
+ 6. Enter the **Origin**, such as `https://www.zohoapis.com.au`.
47
+ 7. Set **Credential** to **OAuth2 credential**.
48
+ 8. Enter the **Token URL** from the provider's documentation. Do not put a secret in the
49
+ token URL.
50
+ 9. Set the **Grant**, the **Client authentication** and the **Header scheme** from the
51
+ provider's documentation.
52
+ 10. Enter the **Client ID**.
53
+ 11. Enter the **Client secret**.
54
+ 12. For the refresh token grant, enter the **Refresh token**.
55
+ 13. Choose **Register external API**.
56
+
57
+ The client secret and the refresh token are write-only. After you save them, Picked shows
58
+ only that they are stored.
59
+
60
+ ## 3. Add a credential to an external API that you registered
61
+
62
+ 1. Open the external API.
63
+ 2. Choose **Use an OAuth2 credential**.
64
+ 3. For Zoho CRM, set **Preset** to the region of your Zoho account. For another provider,
65
+ do steps 8 and 9 of section 2.
66
+ 4. Do steps 10 to 12 of section 2.
67
+ 5. Choose **Save credential**.
68
+
69
+ The credential replaces the authorization header of the external API.
70
+
71
+ ## 4. Refresh only for an expired token
72
+
73
+ Some APIs answer 401 for an error that a new token does not correct. To get a new token only
74
+ for an expired token:
75
+
76
+ 1. Open the external API.
77
+ 2. Choose **Edit OAuth2 credential**.
78
+ 3. In **401 rule: response body path**, enter the path of the error code in the 401 answer,
79
+ such as `code`. Put a dot between the parts of the path, for example `data.0.code`.
80
+ 4. In **401 rule: values**, enter the error codes that show an expired token, such as
81
+ `INVALID_TOKEN`.
82
+ 5. Choose **Save credential**.
83
+
84
+ The Zoho CRM preset sets this rule for you.
85
+
86
+ ## 5. Replace a refresh token that the provider rejected
87
+
88
+ When the provider rejects the credential, the external API page shows the reason in **Last
89
+ refresh failure**. Each call fails with `credential_rejected` until you correct the
90
+ credential.
91
+
92
+ 1. Make a new refresh token.
93
+ 2. Open the external API.
94
+ 3. Choose **Edit OAuth2 credential**.
95
+ 4. Enter the new **Refresh token**.
96
+ 5. Choose **Save credential**.
97
+
98
+ Leave the **Client secret** empty to keep the stored client secret. If you change the
99
+ **Token URL**, the **Client ID** or the **Client authentication**, enter the client secret
100
+ and the refresh token again.
101
+
102
+ ## Related
103
+
104
+ - [External APIs](/reference/dashboard/external-apis#oauth2-credential) — every setting of
105
+ an OAuth2 credential, and when Picked gets a new token.
106
+ - [How to call an external API from a workflow](/how-to/call-an-external-api-from-a-workflow)
107
+ — the node that sends the request.
@@ -0,0 +1,131 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to render a step form
7
+
8
+ This guide shows you how to render the fields of the active step and let a visitor submit
9
+ them.
10
+
11
+ ## 1. Put the steps in a router
12
+
13
+ `StepRouter` watches the step state and activates the `Step` whose `id` matches.
14
+
15
+ :::code-group
16
+
17
+ ```tsx [React]
18
+ import { StepRouter, Step } from '@picked/react/router'
19
+
20
+ function MyExperience() {
21
+ return (
22
+ <StepRouter>
23
+ <Step id="welcome">
24
+ <WelcomeScreen />
25
+ </Step>
26
+ <Step id="contact">
27
+ <ContactForm />
28
+ </Step>
29
+ </StepRouter>
30
+ )
31
+ }
32
+ ```
33
+
34
+ ```svelte [Svelte]
35
+ <script lang="ts">
36
+ import { StepRouter, Step } from '@picked/svelte/router'
37
+ </script>
38
+
39
+ <StepRouter>
40
+ <Step id="welcome">
41
+ <WelcomeScreen />
42
+ </Step>
43
+ <Step id="contact">
44
+ <ContactForm />
45
+ </Step>
46
+ </StepRouter>
47
+ ```
48
+
49
+ :::
50
+
51
+ :::note
52
+ A router must hold every Step, even when there is only one Step.
53
+ :::
54
+
55
+ ## 2. Render the fields
56
+
57
+ Bind `form.props` to your `<form>` element and map over the fields.
58
+
59
+ :::code-group
60
+
61
+ ```tsx [React]
62
+ import { useStep, useStoreGlobal } from '@picked/react/hooks'
63
+
64
+ function StepForm() {
65
+ const { fields, form } = useStep()
66
+ const currentFields = useStoreGlobal(fields)
67
+ const isSubmitting = useStoreGlobal(form.submitting)
68
+
69
+ return (
70
+ <form {...form.props}>
71
+ {currentFields.map((field) => (
72
+ <div key={field.id}>
73
+ <label htmlFor={field.id}>{field.label}</label>
74
+ <input
75
+ id={field.id}
76
+ name={field.id}
77
+ value={field.value ?? ''}
78
+ onChange={field.onChange}
79
+ onBlur={field.onBlur}
80
+ />
81
+ {field.error && field.touched && <p className="error">{field.error.userMessage}</p>}
82
+ </div>
83
+ ))}
84
+ <button type="submit" disabled={isSubmitting}>Continue</button>
85
+ </form>
86
+ )
87
+ }
88
+ ```
89
+
90
+ ```svelte [Svelte]
91
+ <script lang="ts">
92
+ import { getStep } from '@picked/svelte/hooks'
93
+
94
+ const { fields, form } = getStep()
95
+ const isSubmitting = form.submitting
96
+ </script>
97
+
98
+ <form {...form.props}>
99
+ {#each $fields as field}
100
+ <div>
101
+ <label for={field.id}>{field.label}</label>
102
+ <input
103
+ id={field.id}
104
+ name={field.id}
105
+ value={field.value ?? ''}
106
+ onchange={field.onChange}
107
+ onblur={field.onBlur}
108
+ />
109
+ {#if field.error && field.touched}
110
+ <p class="error">{field.error.userMessage}</p>
111
+ {/if}
112
+ </div>
113
+ {/each}
114
+ <button type="submit" disabled={$isSubmitting}>Continue</button>
115
+ </form>
116
+ ```
117
+
118
+ :::
119
+
120
+ The SDK checks a field on blur, and not while the visitor types.
121
+
122
+ ## 3. Load a large step lazily
123
+
124
+ Pass a dynamic import to the `component` prop of `<Step>`. The SDK does not load the
125
+ content until that step becomes active.
126
+
127
+ ## Related
128
+
129
+ - [How to run code on a form submission](/how-to/run-code-on-a-form-submission) — the
130
+ callbacks.
131
+ - [SDK steps](/reference/sdk/steps) — the `StepContext` shape.
@@ -0,0 +1,41 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to render an unpublished preview
7
+
8
+ This guide shows you how to render draft content on your server. Your server cannot read
9
+ the query string of the browser, so you must pass the token yourself.
10
+
11
+ ## 1. Get the token
12
+
13
+ The management API holds the preview token of a workspace in the `previewToken` field. The
14
+ preview action in the dashboard also makes one for you.
15
+
16
+ ## 2. Read the config with the token
17
+
18
+ ```bash
19
+ curl "https://api.picked.app/api/v1/projects/proj_123?preview_token=<token>"
20
+ ```
21
+
22
+ A preview response returns `versionId: null`. An invalid token returns `401`.
23
+
24
+ ## 3. Pass the same token to `serialize()`
25
+
26
+ ```ts
27
+ const snapshot = await serialize(client, {
28
+ projectConfig,
29
+ attributes: { location: { pathname: '/promo' } },
30
+ previewToken,
31
+ })
32
+ ```
33
+
34
+ Use the same token for both requests. The configuration and the content then come from the same
35
+ preview.
36
+
37
+ ## Related
38
+
39
+ - [About the two APIs](/explanation/the-two-apis) — how a preview stays out of the search
40
+ index.
41
+ - [Preview, cache, and crawlers](/reference/delivery-api/preview-caching-crawling).
@@ -0,0 +1,81 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to retry a failed call with a loop
7
+
8
+ This guide shows you how to make a workflow send a request again after the request fails.
9
+ The workflow waits before each new try. It stops after a number of tries that you set.
10
+
11
+ You need a workflow with an **External API** node first. Refer to
12
+ [How to call an external API from a workflow](/how-to/call-an-external-api-from-a-workflow).
13
+
14
+ ## The loop
15
+
16
+ A loop is a path of edges that goes back to a node that the run reached before. This loop
17
+ has three nodes:
18
+
19
+ | Node | Function |
20
+ | ------------------- | ------------------------------------------------ |
21
+ | External API `send` | Sends the request. |
22
+ | Condition `check` | Decides if the workflow sends the request again. |
23
+ | Sleep `pause` | Waits before the next try. |
24
+
25
+ When you save a workflow, Picked refuses a loop that does not have a sleep node and a
26
+ condition node on it. Picked also refuses an edge from a node to the same node.
27
+
28
+ ## 1. Connect the error exit to a condition
29
+
30
+ 1. Open the workflow.
31
+ 2. Choose **Add condition** on the error exit of `send`.
32
+ 3. In the rule of `check`, test `{{ nodes.send.error.retryable }}`.
33
+ 4. Connect the `false` exit of `check` to the nodes that record the failure.
34
+
35
+ `check` runs only when the call fails. The rule can then read the error of the last try.
36
+
37
+ ## 2. Add the sleep node
38
+
39
+ 1. Choose **Add sleep** on the `true` exit of `check`.
40
+ 2. Set **Wait for** to the time between two tries.
41
+ 3. Connect the exit of `pause` to the input of `send`.
42
+
43
+ The editor shows a loop as an edge that goes back to `send`.
44
+
45
+ ## 3. Set the number of tries
46
+
47
+ Each node in the loop runs one time for each try. A run can visit one node a maximum
48
+ number of times. This number is the visit cap of the workflow.
49
+
50
+ 1. Open the workflow settings.
51
+ 2. Set **Visit cap** to a number from 1 to 100. If you do not set it, the visit cap is 10.
52
+
53
+ A node can get to the visit cap. If the run then gets to the node again, the run stops.
54
+ The run shows `Failed` with the code `loop_limit_reached`.
55
+
56
+ To stop the loop before the visit cap, test `current.visit` in the rule of `check`.
57
+ Its value is the number of the visit to `check`. This is also the number of failed tries.
58
+ This rule tries a maximum of 3 times:
59
+
60
+ ```json
61
+ {
62
+ "and": [{ "var": "nodes.send.error.retryable" }, { "<": ["{{ current.visit }}", 3] }]
63
+ }
64
+ ```
65
+
66
+ With this rule, the run continues on the `false` exit of `check` after 3 failures.
67
+
68
+ ## 4. Read the result
69
+
70
+ A node reads the last visit of another node. After the loop, `{{ nodes.send.status }}`
71
+ gives the status of the last try.
72
+
73
+ The **Tasks** card of the run shows each visit as a task. Refer to the
74
+ [Workflow runs reference](/reference/dashboard/workflow-runs#tasks).
75
+
76
+ ## Related
77
+
78
+ - [Workflow runs](/reference/dashboard/workflow-runs) — the tasks of each visit, and the
79
+ visit cap.
80
+ - [Workflow templates](/reference/dashboard/workflow-templates) — the data that a node
81
+ reads in a loop.
@@ -0,0 +1,64 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to reuse a section
7
+
8
+ This guide shows you how to define a common structure one time and use it in more than
9
+ one Model.
10
+
11
+ ## 1. Create the section
12
+
13
+ 1. Click **Pages** in the sidebar.
14
+ 2. Click **Page Sections**.
15
+ 3. Click **New block**.
16
+ 4. Enter a **Name** and a **Key**.
17
+ 5. Add your fields.
18
+ 6. Click **Create block**.
19
+
20
+ The **Category** is **Section** already, because you opened the list from **Page
21
+ Sections**.
22
+
23
+ ## 2. Accept the section in a Model
24
+
25
+ 1. Open a page layout or a step layout.
26
+ 2. Add a **Page Sections** field to the schema.
27
+ 3. Click **Save**.
28
+
29
+ The field now accepts any Block of the Section category. Make the field repeatable to
30
+ build a page from a list of sections.
31
+
32
+ If you want one section at a fixed place instead of a list, add a **Content block** field.
33
+ Set its **Block category** to the **Section** category.
34
+
35
+ ## 3. Choose the section in an entry
36
+
37
+ 1. Open a content entry that uses the Model.
38
+ 2. Find the field that you added.
39
+ 3. Choose your Block.
40
+ 4. Fill in the fields of the Block.
41
+ 5. Click **Save**.
42
+
43
+ :::warning
44
+ An edit to a Block does not migrate the entries that use it. Check each affected content
45
+ entry again, and correct every field error before you publish.
46
+ :::
47
+
48
+ ## API and MCP
49
+
50
+ You can also do this task with the [Management API](/reference/management-api/schema), the [MCP server](/reference/tools/mcp), or code that you send to [`picked_run`](/reference/tools/mcp-run).
51
+
52
+ | Task | GraphQL | MCP tool | Run |
53
+ | --- | --- | --- | --- |
54
+ | Create the section | [`contentBlockCreate`](/reference/management-api/schema/cms-models#contentblockcreate) | [`create_content_block`](/reference/tools/mcp#create_content_block) | `tools.create_content_block({ workspace_id, name, key, schema })` |
55
+ | Accept the section in a Model | [`contentModelUpdate`](/reference/management-api/schema/cms-models#contentmodelupdate) | [`update_content_model`](/reference/tools/mcp#update_content_model) | `tools.update_content_model({ content_model_id, schema })` |
56
+ | Choose the section in an entry | [`contentEntryUpdate`](/reference/management-api/schema/cms-models#contententryupdate) | [`update_content_entry`](/reference/tools/mcp#update_content_entry) | `tools.update_content_entry({ content_entry_id, data })` |
57
+ | Check the entries after you edit a Block | [`workspaceContentEntryValidation`](/reference/management-api/schema/cms-models#workspacecontententryvalidation) | [`validate_workspace_content_entries`](/reference/tools/mcp#validate_workspace_content_entries) | `tools.validate_workspace_content_entries({ workspace_id })` |
58
+
59
+ ## Related
60
+
61
+ - [About the content model](/explanation/the-content-model) — why a schema change does not
62
+ migrate your data.
63
+ - [Blocks](/reference/dashboard/content/blocks) — the reference.
64
+ - [Models](/reference/dashboard/content/models) — every field type.