@picked/client 0.0.1-dev.1776647091821 → 0.0.1-dev.1776662399138

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 (51) hide show
  1. package/dist/{api-BQ0tb4IV.js → api-C5p0q172.js} +1 -1
  2. package/dist/{api-pkh5K05P.cjs → api-CE3ISEsc.cjs} +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
@@ -0,0 +1,411 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Steps
6
+
7
+ Steps are the building blocks of an experience. Each step represents a single screen or stage in a multi-step journey.
8
+
9
+ ## How Steps Work
10
+
11
+ When an experience starts, its steps are connected in a directed node graph. The SDK traverses this graph to determine which step to display:
12
+
13
+ 1. **Initialization**: When the experience opens, the SDK starts at the origin node and traverses the graph to find the first step
14
+ 2. **Step Entry**: The active step is set and a `step_enter` event is emitted on the event bus
15
+ 3. **Field Evaluation**: The step's fields are evaluated — fields with conditions are shown or hidden based on the current state
16
+ 4. **User Input**: The user interacts with the step's fields. Client-side validation runs on blur and on submit
17
+ 5. **Step Submission**: When the user submits, a `step_submit` event fires and field responses are committed to the client state
18
+ 6. **Navigation**: The SDK follows the edge from the current step node to the next, traversing any conditionals or actions before arriving at the next step
19
+ 7. **Completion**: When no further steps exist, the experience flow is complete
20
+
21
+ ## Step Routing
22
+
23
+ `StepRouter` listens to the experience's current step state and activates the matching `Step` by its `id`. Each `Step` registers itself on mount and unregisters on unmount, so lazy-rendered tabs or drawers work automatically.
24
+
25
+ :::code-group
26
+
27
+ ```svelte [Svelte]
28
+ <script lang="ts">
29
+ import { StepRouter, Step } from '@picked/svelte/router'
30
+ </script>
31
+
32
+ <StepRouter>
33
+ <Step id="welcome">
34
+ <WelcomeScreen />
35
+ </Step>
36
+
37
+ <Step id="contact">
38
+ <ContactForm />
39
+ </Step>
40
+
41
+ <Step id="confirmation">
42
+ <ConfirmationScreen />
43
+ </Step>
44
+ </StepRouter>
45
+ ```
46
+
47
+ ```tsx [React]
48
+ import { StepRouter, Step } from '@picked/react/router'
49
+
50
+ function MyExperience() {
51
+ return (
52
+ <StepRouter>
53
+ <Step id="welcome">
54
+ <WelcomeScreen />
55
+ </Step>
56
+
57
+ <Step id="contact">
58
+ <ContactForm />
59
+ </Step>
60
+
61
+ <Step id="confirmation">
62
+ <ConfirmationScreen />
63
+ </Step>
64
+ </StepRouter>
65
+ )
66
+ }
67
+ ```
68
+
69
+ :::
70
+
71
+ :::note
72
+ All Steps must be wrapped in a router, even if it is a single Step
73
+ :::
74
+
75
+ ### Lazy-Loaded Steps
76
+
77
+ For large experiences, steps can be lazily loaded using dynamic imports:
78
+
79
+ :::code-group
80
+
81
+ ```svelte [Svelte]
82
+ <StepRouter>
83
+ <Step id="welcome" component={import('./WelcomeScreen.svelte')} />
84
+ </StepRouter>
85
+ ```
86
+
87
+ ```tsx [React]
88
+ import { lazy } from 'react'
89
+
90
+ const WelcomeScreen = lazy(() => import('./WelcomeScreen'))
91
+
92
+ <StepRouter>
93
+ <Step id="welcome">
94
+ <WelcomeScreen />
95
+ </Step>
96
+ </StepRouter>
97
+ ```
98
+
99
+ :::
100
+
101
+ The `component` prop accepts a `Promise<Component>` — the step's content is not loaded until it becomes active.
102
+
103
+ ## Accessing Step Context
104
+
105
+ Inside a `Step` component (or any component rendered within one), call `getStep()` / `useStep()` to access the step's reactive context:
106
+
107
+ :::code-group
108
+
109
+ ```svelte [Svelte]
110
+ <script lang="ts">
111
+ import { getStep } from '@picked/svelte/hooks'
112
+
113
+ const { step, fields } = getStep()
114
+ </script>
115
+
116
+ <h1>{$step.label}</h1>
117
+
118
+ {#each $fields as field}
119
+ <label>{field.label}</label>
120
+ <input
121
+ name={field.id}
122
+ value={field.value ?? ''}
123
+ onchange={field.onChange}
124
+ onblur={field.onBlur}
125
+ />
126
+ {#if field.error && field.touched
127
+ <p class="error">{field.error.userMessage}</p>
128
+ {/if}
129
+ {/each}
130
+ ```
131
+
132
+ ```tsx [React]
133
+ import { useStep } from '@picked/react/hooks'
134
+ import { useUnit } from 'effector-react'
135
+
136
+ function ContactForm() {
137
+ const { step, fields } = useStep()
138
+ const currentStep = useUnit(step)
139
+ const currentFields = useUnit(fields)
140
+
141
+ return (
142
+ <>
143
+ <h1>{currentStep.label}</h1>
144
+
145
+ {currentFields.map((field) => (
146
+ <div key={field.id}>
147
+ <label>{field.label}</label>
148
+ <input
149
+ name={field.id}
150
+ value={field.value ?? ''}
151
+ onChange={field.onChange}
152
+ onBlur={field.onBlur}
153
+ />
154
+ {field.error && field.touched && <p className="error">{field.error.userMessage}</p>}
155
+ </div>
156
+ ))}
157
+ </>
158
+ )
159
+ }
160
+ ```
161
+
162
+ :::
163
+
164
+ `getStep()` (Svelte) / `useStep()` (React) returns a `StepContext` with the following properties:
165
+
166
+ | Property | Type | Description |
167
+ | ---------- | --------------------------------------------- | ------------------------------------------------------------------------------ |
168
+ | `step` | `Store<ExperienceStep>` | Reactive store containing the step's `id` and `label` |
169
+ | `fields` | `Store<FormField[]>` | Reactive store of fields with their current values, errors, and event handlers |
170
+ | `isActive` | `Store<boolean>` (Svelte) / `boolean` (React) | Whether this step is currently active in the router |
171
+ | `form` | Object | Form submission handlers (see Forms section) |
172
+
173
+ ## Forms
174
+
175
+ When a step has fields, `getStep()` / `useStep()` provides a `form` object for handling submission, validation, and navigation.
176
+
177
+ ### Handling Submission
178
+
179
+ Bind `form.props` to your `<form>` element's `onsubmit` handler:
180
+
181
+ :::code-group
182
+
183
+ ```svelte [Svelte]
184
+ <script lang="ts">
185
+ import { getStep } from '@picked/svelte/hooks'
186
+
187
+ const { fields, form } = getStep()
188
+ const isSubmitting = form.submitting
189
+ </script>
190
+
191
+ <form {...form.props}>
192
+ {#each $fields as field}
193
+ <input
194
+ name={field.id}
195
+ value={field.value ?? ''}
196
+ onchange={field.onChange}
197
+ onblur={field.onBlur}
198
+ />
199
+ {#if field.error && field.touched}
200
+ <p class="error">{field.error.userMessage}</p>
201
+ {/if}
202
+ {/each}
203
+
204
+ <button type="submit" disabled={$isSubmitting}>
205
+ {$isSubmitting ? 'Submitting…' : 'Continue'}
206
+ </button>
207
+ </form>
208
+ ```
209
+
210
+ ```tsx [React]
211
+ import { useStep } from '@picked/react/hooks'
212
+ import { useUnit } from 'effector-react'
213
+
214
+ function StepForm() {
215
+ const { fields, form } = useStep()
216
+ const currentFields = useUnit(fields)
217
+ const isSubmitting = useUnit(form.submitting)
218
+
219
+ return (
220
+ <form {...form.props}>
221
+ {currentFields.map((field) => (
222
+ <div key={field.id}>
223
+ <input
224
+ name={field.id}
225
+ value={field.value ?? ''}
226
+ onChange={field.onChange}
227
+ onBlur={field.onBlur}
228
+ />
229
+ {field.error && field.touched && <p className="error">{field.error.userMessage}</p>}
230
+ </div>
231
+ ))}
232
+
233
+ <button type="submit" disabled={isSubmitting}>
234
+ {isSubmitting ? 'Submitting…' : 'Continue'}
235
+ </button>
236
+ </form>
237
+ )
238
+ }
239
+ ```
240
+
241
+ :::
242
+
243
+ Submitting the form:
244
+
245
+ 1. Collects field values from the HTML form
246
+ 2. Runs `onSubmit` transform callbacks (if any)
247
+ 3. Validates all required fields and field validation rules
248
+ 4. Commits the responses to the experience state
249
+ 5. Fires `form_submit_success` or `form_submit_error` on the event bus
250
+ 6. Navigates to the next step (unless `stopNavigation` is called)
251
+
252
+ ### Transforming Data Before Submission
253
+
254
+ Use `form.onBeforeSubmit` to intercept and transform field data before it is committed. Callbacks run in series, each receiving the output of the previous:
255
+
256
+ :::code-group
257
+
258
+ ```svelte [Svelte]
259
+ <script lang="ts">
260
+ import { getStep } from '@picked/svelte/hooks'
261
+
262
+ const { form } = getStep()
263
+
264
+ form.onBeforeSubmit(async (data) => {
265
+ // Normalize email to lowercase before committing
266
+ return {
267
+ ...data,
268
+ 'email-address': String(data['email-address']).toLowerCase(),
269
+ }
270
+ })
271
+ </script>
272
+
273
+ <form {...form.props}>{/* ... */}</form>
274
+ ```
275
+
276
+ ```tsx [React]
277
+ import { useStep } from '@picked/react/hooks'
278
+
279
+ function ContactStep() {
280
+ const { form } = useStep()
281
+
282
+ form.onBeforeSubmit(async (data) => {
283
+ // Normalize email to lowercase before committing
284
+ return {
285
+ ...data,
286
+ 'email-address': String(data['email-address']).toLowerCase(),
287
+ }
288
+ })
289
+
290
+ return <form {...form.props}>{/* ... */}</form>
291
+ }
292
+ ```
293
+
294
+ :::
295
+
296
+ ### Success and Error Callbacks
297
+
298
+ Register callbacks for submission outcomes using `form.onSuccess` and `form.onError`:
299
+
300
+ :::code-group
301
+
302
+ ```svelte [Svelte]
303
+ <script lang="ts">
304
+ import { getStep } from '@picked/svelte/hooks'
305
+
306
+ const { form } = getStep()
307
+
308
+ form.onSuccess(async ({ data }) => {
309
+ // Call your backend after responses are committed
310
+ await fetch('/api/submit', {
311
+ method: 'POST',
312
+ body: JSON.stringify(data),
313
+ })
314
+ })
315
+
316
+ form.onError((errors) => {
317
+ console.error('Validation errors:', errors)
318
+ })
319
+ </script>
320
+
321
+ <form {...form.props}>{/* ... */}</form>
322
+ ```
323
+
324
+ ```tsx [React]
325
+ import { useStep } from '@picked/react/hooks'
326
+
327
+ function ContactStep() {
328
+ const { form } = useStep()
329
+
330
+ form.onSuccess(async ({ data }) => {
331
+ // Call your backend after responses are committed
332
+ await fetch('/api/submit', {
333
+ method: 'POST',
334
+ body: JSON.stringify(data),
335
+ })
336
+ })
337
+
338
+ form.onError((errors) => {
339
+ console.error('Validation errors:', errors)
340
+ })
341
+
342
+ return <form {...form.props}>{/* ... */}</form>
343
+ }
344
+ ```
345
+
346
+ :::
347
+
348
+ Callbacks registered via `onSuccess` and `onError` are automatically cleaned up when the component unmounts — no manual teardown is needed.
349
+
350
+ ### Preventing Navigation on Success
351
+
352
+ By default, the experience advances to the next step after a successful submission. Call `stopNavigation()` inside `onSuccess` to prevent this, for example when you need to handle navigation manually:
353
+
354
+ :::code-group
355
+
356
+ ```svelte [Svelte]
357
+ <script lang="ts">
358
+ import { getStep } from '@picked/svelte/hooks'
359
+ import { getExperience } from '@picked/svelte/hooks'
360
+
361
+ const { form } = getStep()
362
+ const experience = getExperience()
363
+
364
+ form.onSuccess(async ({ data, stopNavigation }) => {
365
+ stopNavigation()
366
+
367
+ // Navigate to step based on submitted data
368
+ if (data['plan-type'] === 'enterprise') {
369
+ experience.goToStepById('enterprise-contact')
370
+ } else {
371
+ experience.goToNextStep()
372
+ }
373
+ })
374
+ </script>
375
+
376
+ <form {...form.props}>{/* ... */}</form>
377
+ ```
378
+
379
+ ```tsx [React]
380
+ import { useStep } from '@picked/react/hooks'
381
+ import { useExperience } from '@picked/react/hooks'
382
+
383
+ function PlanStep() {
384
+ const { form } = useStep()
385
+ const experience = useExperience()
386
+
387
+ form.onSuccess(async ({ data, stopNavigation }) => {
388
+ stopNavigation()
389
+
390
+ // Decide the next step based on submitted data
391
+ if (data['plan-type'] === 'enterprise') {
392
+ experience.goToStepById('enterprise-contact')
393
+ } else {
394
+ experience.goToNextStep()
395
+ }
396
+ })
397
+
398
+ return <form {...form.props}>{/* ... */}</form>
399
+ }
400
+ ```
401
+
402
+ :::
403
+
404
+ ## Best Practices
405
+
406
+ - **Give steps meaningful labels**: The `label` is used in analytics events and the dashboard — prefer descriptive names like `"Contact Details"` over `"Step 2"`
407
+ - **Keep steps focused**: Each step should collect a small, related set of information. Prefer more steps over longer forms to reduce abandonment
408
+ - **Use conditionals for branching**: Avoid duplicating steps — model branching logic with `nextConditional()` so shared steps can be reused
409
+ - **Use `stopNavigation` sparingly**: Automatic navigation after submit is the expected behavior. Only disable it when you need explicit control over the next destination
410
+ - **Validate eagerly on blur, not on change**: The SDK follows this pattern automatically — fields show errors after the user leaves them, not as they type
411
+ - **Lazy-load large step components**: Use the `component` prop on `<Step>` with a dynamic import for content that is only shown to a subset of users
@@ -0,0 +1,35 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Steps
6
+
7
+ A step is a visual screen or card shown to visitors during an experience. Each step holds one or more fields that collect data or display content.
8
+
9
+ ## Create a step
10
+
11
+ 1. Open your project.
12
+ 2. Click **Steps** in the sidebar.
13
+ 3. Click **New Step**.
14
+ 4. Enter a name and key.
15
+ 5. Click **Create**.
16
+
17
+ The key is a unique identifier used in your application. It's set once and can't be changed later.
18
+
19
+ ## Configure a step
20
+
21
+ **Name**: The display name shown in the dashboard. Not visible to visitors.
22
+
23
+ **Key**: The programmatic identifier used by your application to reference this step. Not visible to visitors.
24
+
25
+ **Layout Name**: The name of the component layout used to render this step. For example, `card`, `modal`, or `fullscreen`.
26
+
27
+ ## Assign fields
28
+
29
+ In the flow builder, drag a field from the entity browser onto a step node. The field appears in the step's **Assigned Fields** list in the inspector.
30
+
31
+ You can also view and manage assigned fields by selecting the step node and expanding the **Assigned Fields** section. Click any field to open its inspector.
32
+
33
+ ## Reuse steps
34
+
35
+ Steps are project-level entities. You can drag the same step into multiple experience flows. Changes to a step's configuration apply everywhere it's used.
@@ -0,0 +1,40 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Team management
6
+
7
+ Manage who has access to your organization and what they can do. Organizations are the top-level container for projects and members.
8
+
9
+ ## Organizations
10
+
11
+ An organization groups your projects, members, and settings. You can belong to multiple organizations.
12
+
13
+ Switch between organizations using the organization selector in the top of the sidebar.
14
+
15
+ ## Invite members
16
+
17
+ 1. Open your organization.
18
+ 2. Click **Members**.
19
+ 3. Click **Invite**.
20
+ 4. Enter an email address.
21
+ 5. Select a role.
22
+ 6. Click **Send Invite**.
23
+
24
+ The invited user receives an email with a link to join your organization. They need to create a Picked account if they don't have one.
25
+
26
+ ## Remove members
27
+
28
+ 1. Open your organization.
29
+ 2. Click **Members**.
30
+ 3. Find the member you want to remove.
31
+ 4. Click **Remove** next to their name.
32
+ 5. Confirm the removal.
33
+
34
+ The member immediately loses access to all projects in the organization.
35
+
36
+ ## Roles and permissions
37
+
38
+ Members are assigned roles that control what they can access. Roles determine whether a member can create projects, edit experiences, manage other members, or view reports.
39
+
40
+ Assign the minimum role needed for each member's responsibilities.
@@ -0,0 +1,29 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Chrome Extension
6
+
7
+ The Picked Developer Tools extension adds a Picked panel to Chrome DevTools for inspecting and debugging your Picked integration on any page running the SDK.
8
+
9
+ ![Picked Developer Tools](/chrome-extension-screenshot.jpg)
10
+
11
+ ## Installation
12
+
13
+ Install from the [Chrome Web Store](https://chromewebstore.google.com/detail/picked-developer-tools/nimmdamgnhlhdpngnalkpmnleebngidh).
14
+
15
+ ## Setup
16
+
17
+ Add the `svelteDevtoolsPlugin` to your Picked client so the extension can connect to the SDK.
18
+
19
+ ```typescript
20
+ import { createPicked } from '@picked/client'
21
+ import { svelteDevtoolsPlugin } from '@picked/svelte/plugins/devtools'
22
+
23
+ const picked = createPicked({
24
+ projectId: 'your-project-id',
25
+ plugins: [
26
+ svelteDevtoolsPlugin(),
27
+ ],
28
+ })
29
+ ```
@@ -0,0 +1,69 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Google Tag Manager template
6
+
7
+ The Picked Ecommerce GTM Tag Template lets you track ecommerce events and send them to Picked directly from Google Tag Manager.
8
+
9
+ ## Automatic installation
10
+
11
+ Install the template from the GTM Community Gallery:
12
+
13
+ 1. In Tag Manager, click **Templates**.
14
+ 2. In the **Tag Templates** section, click **Search Gallery**.
15
+ 3. Search for `Picked Ecommerce`.
16
+ 4. Click the Picked Ecommerce template to view details.
17
+ 5. Click **Add to workspace**.
18
+ 6. Review the required permissions and click **Add**.
19
+
20
+ ![Picked Ecommerce template in the GTM Community Gallery](/gtm/picked-ecommerce-gtm.png)
21
+
22
+ ## Manual installation
23
+
24
+ If you prefer to import the template file directly:
25
+
26
+ 1. Download the [template file](https://github.com/picked-app/gtm-template/blob/main/template.tpl) from GitHub (use the download icon).
27
+ 2. In Google Tag Manager, go to **Templates**.
28
+ 3. Click **New**.
29
+ 4. Open the three-dot menu and click **Import**.
30
+ 5. Select the downloaded template file.
31
+ 6. Click **Save**.
32
+
33
+ ## Setup
34
+
35
+ ### 1. Create a Tag ID variable
36
+
37
+ Sign in to the [Picked Dashboard](https://dashboard.picked.app) and copy your project Tag ID. Save it as a constant variable in GTM (e.g. `Picked Tag ID`).
38
+
39
+ ![Creating a Picked Tag ID variable in GTM](/gtm/gtm-variable.png)
40
+
41
+ ### 2. Create an ecommerce data variable
42
+
43
+ Create a **Data Layer Variable** with the variable name set to `ecommerce`. This captures the GA4 Enhanced Ecommerce data layer.
44
+
45
+ ![GA4 Enhanced Ecommerce data layer variable](/gtm/gtm-ecommerce-variable.png)
46
+
47
+ ### 3. Create event triggers
48
+
49
+ Add a trigger for each ecommerce event you want to track (e.g. `add_to_cart`, `purchase`, `view_item`).
50
+
51
+ ![Event triggers for ecommerce tracking](/gtm/gtm-triggers.png)
52
+
53
+ ### 4. Create tags
54
+
55
+ Create a new tag for each event, selecting the **Picked Ecommerce** tag template. Assign the matching trigger and configure the options below.
56
+
57
+ ![Tags using the Picked Ecommerce template](/gtm/gtm-tags.png)
58
+
59
+ ## Configuration options
60
+
61
+ | Option | Description |
62
+ | --- | --- |
63
+ | **Tag ID** | Your Picked project Tag ID. Find it in the [Picked Dashboard](https://dashboard.picked.app). |
64
+ | **Event** | The ecommerce event to track (e.g. `add_to_cart`, `purchase`). |
65
+ | **Send ecommerce data** | Enable this to send GA4 Enhanced Ecommerce data to Picked. Should be enabled for all ecommerce events. |
66
+ | **GA4 Enhanced Ecommerce Data** | The GTM variable containing GA4 ecommerce data. Use the data layer variable created in step 2. |
67
+ | **Additional data** | Optional GTM variable for sending extra metadata with the event. |
68
+
69
+ ![Picked Ecommerce tag configuration](/gtm/gtm-tag.png)
@@ -0,0 +1,19 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Tooling
6
+
7
+ Build tools and developer utilities for working with Picked.
8
+
9
+ ## Vite Plugin
10
+
11
+ The [`@picked/vite-plugin-picked`](/tooling/vite-plugin) package integrates Picked into your Vite-based project. It fetches your project configuration at build time and provides a local launcher for previewing experiences during development.
12
+
13
+ [Get started with the Vite Plugin &rarr;](/tooling/vite-plugin)
14
+
15
+ ## Chrome Extension
16
+
17
+ The [Picked Developer Tools](/tooling/chrome-extension) extension adds a dedicated panel to Chrome DevTools for inspecting and debugging your Picked integration on any page running the SDK.
18
+
19
+ [Get started with the Chrome Extension &rarr;](/tooling/chrome-extension)