Widget Builder Best Practices
Practical tips from teams that have shipped widgets to production. Skim before you start, revisit before each release.
Decide if you really need a widget
Before opening Widget Builder, ask: can I do this with a rich message or a flow?
| Use case | Reach for |
|---|---|
| Quick replies, a carousel, a single short input | Rich message node |
| Custom UI with multiple fields, conditional logic, or its own UI state | Widget |
| Branching logic with no UI | Flow or agent |
Widgets are powerful, but they cost more to build and maintain than rich messages. Use them when you genuinely need custom UI.
Schema design
- Keep inputs flat. Deeply nested
propertiesare harder to bind in flow nodes and harder to debug in traces. - Mark requireds explicitly. Without the
requiredarray, the flow node won't warn you about unfilled inputs at design time. - Use
enumfor known sets. "status: pending | approved | rejected" is clearer and validates for free. - Don't over-ask. Inputs you don't actually use just slow everyone down — yourself included.
State design
- Default state is for local UI only. The selected option, the active step, the form errors. That's it.
- Don't put session or account data here. That belongs in agent state, fetched from a tool, or passed in via schema.
- Keep state shallow. A flat object with primitives is the safest to serialize and the easiest to debug.
- Don't duplicate schema inputs. If
userNamecame in via schema, read it from props — don't copy it into state.
Output design
- Match output keys to what the next flow node expects. If the next node reads
selectedSlotId, name your outputselectedSlotId. Renaming downstream is more painful than picking the right name once. - Emit on submit, not on every change. Continuous emissions clutter traces and rack up turn counts.
- Keep output flat. Same reasoning as schema — agents read these by key.
Naming and slugs
- Names are user-facing; slugs are stable. The slug (
widget_TkpB0njZg9) is auto-generated and used by flows. Rename a widget freely — the slug doesn't change, so existing flows keep working. - Pick descriptive names.
BookingForm — APACbeatsForm2. Names appear in the saved-widget list and in the flow's rich-media picker.
Versioning (or lack thereof)
There is no built-in versioning of widget content. Saving overwrites the previous content. For a major redesign:
- Save a copy of the current widget under a new name (
MyWidget — v2 draft). - Wire the copy into a test flow.
- Validate end-to-end in the Playground (▶ on the agent that emits the widget).
- Switch your production flow over to the new widget.
- Delete the old one (or keep it around briefly as a rollback option).
Performance
- Cap widget complexity. Hundreds of dynamic children in one widget will hurt mobile rendering. If a widget feels heavy, split it across two flow nodes that each show a smaller widget.
- Avoid heavy work in render. Move computation into event handlers or out to a tool call.
- Watch the trace. If a turn that includes a widget has surprising latency, the trace will show whether it's the widget render or something upstream.
When to use widgets vs. simpler alternatives
Use a widget when:
- You need persistent local state across user clicks within the same node.
- The UI has conditional logic (show this if that).
- The interaction is multi-step.
Use a rich message when:
- The interaction is a single quick reply, carousel, or short input.
- You don't need local state.
- A standard component would do.
Use a flow when:
- The branching is purely backend logic, no UI involved.
Voice considerations
If your widget can be triggered from a voice channel:
- Provide a voice-friendly fallback for any UI-only affordance. A button with no spoken equivalent confuses voice users.
- Keep the output shape identical for voice and chat. The agent should be channel-agnostic when reading your output.
- Test the voice path explicitly in the Voice Playground — TTS phrasing, pacing, and barge-in behave differently than chat.
Pre-launch checklist
Before pushing a widget into a live conversation:
- Schema marks every required input.
- Default state has values for every key the JSX reads.
- Output keys match what the next workflow node expects.
- Widget renders correctly with realistic test inputs in the builder preview.
- Widget renders inside the Playground (▶ on the agent), not just in the builder preview.
- The agent reads the widget output correctly in the next turn (re-send the same prompt and watch the reply).
- Voice fallback exists if the widget runs on voice.
Common mistakes
- Building a widget for a one-field input. Use a prompt node instead.
- Forgetting
requiredin schema. Loops back as confused flow owners and silent failures. - Naming widgets
Form1,Form2. Two months later, nobody knows which one is which. - Editing a live widget's schema without warning. Breaks every flow that depends on it. Coordinate with flow owners first.
- Skipping the Playground test. "It looked fine in the builder preview" is not the same as "it works in a real conversation."