# Produktly widget concepts for agents

## What you can create

| Widget | What it is | Must have |
|---|---|---|
| **Product tour** | Step-by-step guided flow. Best for "show me how" moments | at least one step |
| **Checklist** | Persistent onboarding to-do list, completed across sessions | at least one item |
| **Smart tip** | Small contextual card anchored to one element | `settings.cssSelector` |
| **Announcement** | Topbar, popup or toast message | `title`, plus `content` for popup and toast |
| **Micro survey** | Short in-app survey, one question at a time | at least one question |
| **NPS widget** | Rating prompt with an optional follow-up | nothing beyond targeting |
| **Feedback widget** | Floating beacon collecting categorized feedback | at least one option |

Every one of them also needs targeting before it can be published. Changelog posts are writable too, through the changelog tools.

A strong onboarding setup pairs types: a checklist whose items launch tours, or a smart tip pointing at the feature a tour just introduced.

## Step types (tours)

`welcome` (centered intro card), `highlight` (spotlights an element — requires `domQuery`), `modal` (centered card), `toast` (bottom-right card), `redirect` (navigates — requires `redirectUrl`, renders nothing).

## Per-type notes worth knowing before you build

- **Smart tips** need a `settings.cssSelector` or nothing renders. Do not combine `displayBeacon: false` with a `beacon_click`/`beacon_hover` trigger: the tip becomes unopenable, and the API rejects it.
- **Announcements** pick a style with `settings.type` (`topbar`, `topbar-hovering`, `popup`, `toast`). Topbar styles render the title only, so `content` is stored but ignored there. Turning an action button on requires giving it a destination.
- **Micro surveys** run questions in a flat list with no branching. Types are `emoji`, `thumbs`, `rating` (fixed 1-5), `text` and `multi-choice`; only `emoji` takes `options` and only `multi-choice` takes `multiChoiceOptions`. Unless `displayTrigger` is `immediate`, `time_delay` or `manual`, you must supply a `cssSelector`, because the default click trigger needs an element to attach to.
- **NPS widgets** support three scales via `settings.selectedScale`: `ten` (0-10, the standard NPS scale), `five` (1-5) and `emojis` (0-2). Scores are calculated per scale.
- **Feedback widgets** take either an explicit `options` array (each option needs a `name`) or `optionsPreset: 1-6` for a built-in set. Note `options` is replaced wholesale on update, not merged.

## Targeting (critical — read this)

A widget with NO targeting never shows anywhere. Always set targeting before publishing:
- `targeting: { showOnAllPages: true }` — everywhere.
- `targeting: { rules: [{ type: "url", comparisonOperator: "contains", value: "/dashboard" }], logicalOperator: "AND" }` — rule-based (url, device_type, language, custom user attributes as `custom::::<key>`, and more).
- `targeting: { userSegmentIds: [3] }` — reuse dashboard-defined segments.

## The draft → review → publish flow

1. Create via the API/MCP: widgets are DRAFTS by default (`active: false`). Drafts never render on the customer site.
2. Every create/update response includes a `dashboardUrl` — give it to the human for visual review in the builder.
3. Publishing (setting `active: true`) requires a publishable widget: targeting set, plus whatever that type must have (see the table above). If anything is missing the widget stays a draft and the response `note` says why.
4. Default to leaving widgets as drafts and letting the human publish from the dashboard, unless they explicitly ask you to publish.
5. Live widgets can be edited, but the API refuses updates that would leave a live widget unpublishable. Set `active: false` first to take one down.

## Practical guardrails

- Fetch before update: updates replace `steps`, `items` and feedback `options` wholesale — send the full array back and PRESERVE existing `id`s (analytics and completion state key on them). `settings` merge instead, so you can send just the keys you are changing, and you can never remove a settings key through the API.
- Integration fields (`slackWebhookUrl`, `discordWebhookUrl`, `zapierWebhookUrl`, `customWebhookUrl`, `notificationEmails`) can be written but are never returned by read calls. Because settings merge, you can edit a widget without resending them.
- Content fields are HTML (sanitized at render) and support `{{user.attr | fallback}}` templates.
- Field names matter: it is `domQuery` (not selector), `nextBtnText` (not nextText), `redirectToUrl` on checklist items but `redirectUrl` on redirect steps. Validation errors tell you exactly what's wrong — read them.
