Menu
guides
Widgets in chat
Attach a widget to a tool and map its response onto a product grid, a cart or your own HTML — including columns per screen width, the editable cart drawer, and what the assistant is and isn't told.
On this page
What a widget is
A widget is a piece of chat interface — a product grid, a cart, your own HTML — that an assistant shows instead of describing something in words.
You attach a widget to a tool, on the Widgets tab of the MCP server that exposes it. When the tool answers, its response is mapped onto the widget and rendered as cards in the chat.
The mapping is the point. Your workflow returns whatever its source system naturally returns — sku, name, href — and never has to know a product card exists. You say once, against the tool, which field feeds which part of the card. Changing how something looks is not a workflow edit.
Why this matters more than it looks
The assistant is never told your prices. When a tool has a widget attached, the model receives only a count and up to twelve titles; the actual values go straight to the renderer.
That is deliberate. A language model asked to repeat a price will sometimes round it, convert it, or invent one for a product it half-remembers. A widget is the only thing on screen that can state a price, and it states exactly what your workflow returned.
The practical consequence: the assistant's reply will be one short line like "Here are a few that might suit" — it is explicitly told the results are already on screen and not to list them. If you want the assistant to discuss the items, it needs a tool without a widget.
Attaching a widget
- Open the MCP server that exposes the tool.
- Go to the Widgets tab. Every tool on the server is listed.
- Pick a widget from the dropdown. Choosing No widget — plain text leaves the tool answering in words, exactly as it does today.
- Map the fields.
- Save.
Tools that are already configured start collapsed, with a summary of what they map, so a server with a dozen tools is still a list you can scan.
Mapping fields
Each widget declares what it needs. Every field is one row with two parts: where the value comes from, and what to do with it.
From response reads a path out of the tool's answer. Paths are dotted, and arrays are indexed:
| Path | Reads |
|---|---|
name | the top-level name |
price.formatted | formatted inside price |
images[0].url | the first image's url |
Fixed value ignores the response and uses what you type. This is right for anything the source system has no opinion about — a button label, a badge that always says "In stock".
Where the list lives
A widget that renders a list (the product grid, a custom widget repeating over items) asks for a source: the path to the array inside the response. If your tool answers { "found": 3, "results": [ … ] }, the source is results, and every field path is then relative to one entry.
The cart is both at once: totals at the top level, and a line items source for the array of lines.
Required fields
Fields marked required are exactly that. An entry missing one is dropped, not rendered half-empty — a card with no title is a broken payload, and showing an empty box hides the mistake. If a grid renders nothing at all, an unmapped required field is the first thing to check; the preview will say so.
Options
Some fields offer options. The one you will use is Trim after (characters) on descriptions, which cuts on a word boundary where there is one nearby and adds an ellipsis, so a trimmed sentence doesn't end mid-word.
The widgets
Product grid
A grid of cards: image, title, description, price, was-price, and an optional Add button.
The Add button sends the card's Reference back to the assistant as an ordinary message, so your cart tool runs, quotas apply and the price is re-resolved on your side. The reference is never shown — it is whatever your workflow can act on, a SKU or a variant id. A product with no reference renders as not available to add rather than offering a button that does nothing.
Columns are three numbers rather than one, because the same grid renders in a 400px chat panel, a full-page assistant and a phone:
| Setting | Applies at | Default |
|---|---|---|
| Columns on a narrow panel | under 360px | 1 |
| Columns on a chat panel | 360–720px | 2 |
| Columns on a wide panel | 720px and up | 3 |
Two things about those widths:
- They are measured on the grid's own container, not the browser window. The chat is an iframe whose width you set, so a window-based rule would tell a narrow panel on a large monitor that it has a monitor's room.
- The grid never uses more columns than there are products. Four columns holding two cards would strand them at half the width they could have had, so a two-product response shows two columns whatever you configured.
Single product
The same card, for a tool that answers with one product rather than a list. It has no column or spacing-between-cards settings, because there is nothing to lay out.
Cart summary
The customer's basket: a heading, the lines, and a summary panel with subtotal, total, a note and a checkout link. On a panel wider than 520px the summary sits beside the lines; below that it stacks underneath, which is what a cart looks like on a phone anyway.
Map a line reference on each line to make the cart editable. With it, the card offers View cart, which opens a slide-over drawer — like a storefront's — where the customer can change quantities and remove lines. Without it the lines are read-only, because there would be nothing to send back.
Every change in the drawer runs your cart tool again and replaces what's on screen. The browser never edits the cart locally, so what the customer sees is always what your server says is in the basket.
Custom HTML
Your own markup and CSS, for anything the built-in widgets don't cover.
Name the values you need, map each to a path in the response, and reference them in the template as {{name}}. Three extras are always available inside a repeated item: {{@index}} (from zero), {{@number}} (from one) and {{@count}}.
For lists, put the per-item markup in the template and the surrounding markup in the wrapper, with {{items}} marking where the items go. That is how you build a carousel or a numbered list: the wrapper holds the scroller, the template is one slide.
Your CSS is rendered inside a shadow root, so it applies to your markup and cannot reach the chat around it — a button { display: none } in your stylesheet will not hide the assistant's own send button. Markup is sanitised and mapped values are escaped, so a product title containing angle brackets stays text rather than becoming markup. Scripts, iframes, forms and inline event handlers are removed.
Examples and the preview
Each widget ships with examples. Choosing one fills in the markup, the mapping, the style and a sample response, so the preview renders immediately — an example that needed its own sample pasted in first would teach nothing.
The preview runs the same transform production does, on the server. What you see is what a customer sees, including a description trimmed to a word boundary and an entry dropped for a missing required field.
Paste a real response from your tool into the sample box and the preview follows it as you type. The full-screen button shows the widget inside a stand-in chat, at a width you choose, so you can check how it reads with the transcript's spacing around it rather than on a bare background. Under the preview is the width it is actually rendering at and which setting that puts in play — useful when a column count appears not to apply.
When it doesn't look right
Nothing renders. A required field is unmapped, or its path is wrong. The preview names the problem.
The assistant describes the products instead of showing cards. The widget isn't attached to the tool that answered, or the response shape moved and the source path no longer finds the array.
A column setting seems ignored. Either the panel is in a different band — check the width under the preview — or the sample has fewer products than the columns you asked for.
The cart is read-only. No line reference is mapped.
Going live
- Attach widgets to the tools whose answers are worth showing rather than saying.
- Paste a real response into the sample box, not a tidied one.
- Check the preview at your embed's width and at full screen.
- For carts, map the line reference and try the drawer.
- Save, then try it in the assistant end to end — the Add button's round trip through your cart tool is the part worth seeing work.