Skip to main content

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 caseReach for
Quick replies, a carousel, a single short inputRich message node
Custom UI with multiple fields, conditional logic, or its own UI stateWidget
Branching logic with no UIFlow 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 properties are harder to bind in flow nodes and harder to debug in traces.
  • Mark requireds explicitly. Without the required array, the flow node won't warn you about unfilled inputs at design time.
  • Use enum for 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 userName came 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 output selectedSlotId. 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 — APAC beats Form2. 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:

  1. Save a copy of the current widget under a new name (MyWidget — v2 draft).
  2. Wire the copy into a test flow.
  3. Validate end-to-end in the Playground (▶ on the agent that emits the widget).
  4. Switch your production flow over to the new widget.
  5. 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 required in 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."