1. What follow‑ups are in the ChatGPT App and why you need them
Let’s start with a plain, non-marketing definition. A follow‑up in the context of the ChatGPT App is a programmatically initiated next step in the dialog. It’s usually a short hint presented as a button: when pressed, it turns into a new text message from the user in the chat and kicks off the next step in the conversation between the person and the AI.
From the model’s perspective, a follow‑up is simply another user message. No magic: when the user clicks your “Show cheaper options” button, something like “Show cheaper gifts” lands in the chat history. The model sees it as a normal user utterance, applies the system prompt and tool descriptions, decides which tool to call, and may re-render your widget with different data.
Why this matters:
- The model thinks in terms of text. If you call a tool directly on click (without a message), you bypass the system’s “brain” and lose part of the context. A follow‑up preserves the conversation’s progress in history and helps the model understand what’s going on.
- The user stays in the familiar chat paradigm: they either type messages or tap suggestions. Follow‑ups are the same kind of quick replies you see in modern messengers.
- It’s the primary bridge between your UI and ChatGPT’s text side. Without follow‑ups, a widget becomes a mute island: the user clicks around and isn’t quite sure what to do next.
You can think of follow‑ups as “the next question to the AI,” phrased by you. We’ll look at follow‑ups not as “just another UI feature,” but as the main way to build a dialog around a widget.
2. “Dialog around the widget”: the conversational sandwich
To see how follow‑ups help build a “dialog around the widget,” it’s useful to imagine the dialog as a three-layer sandwich:
- on top, ChatGPT’s text before the widget (pre‑text),
- in the middle, your widget (UI),
- at the bottom, follow‑ups and subsequent messages (post‑interaction).
Schematic:
sequenceDiagram
participant U as User
participant G as ChatGPT
participant W as Widget
U->>G: "Pick a gift for my sister up to $100"
G->>G: Decides to call the App
G->>U: Pre-text: "I’ll open GiftGenius and find some ideas"
G->>W: Passes toolOutput for rendering
W-->>U: Gift cards + follow-up buttons
U->>W: Click "Show cheaper options"
W->>G: sendFollowUpMessage("Show cheaper gifts, up to $50")
G->>G: New model run, tool calls
G->>W: New toolOutput, updated widget
The pre‑text is usually generated entirely by the model based on the system prompt and context: it “explains” what’s about to happen (“I’ll open an app that helps pick a gift”). We’ll manage this layer later in the module on instructions.
The widget is your familiar React component: it renders the toolOutput, uses widgetState, and provides buttons and choices.
The post‑interaction layer is what we’re dealing with today. Through follow‑ups and sending messages, you define a clear next route: “Show more expensive,” “Change budget,” “Start over,” “Proceed to checkout.”
Put simply, a follow‑up is a control utterance that closes the loop: UI → text → new UI.
3. The technical model: how a follow‑up click turns into a new tool call
Let’s carefully look at the chain of events under the hood. It helps to think of it as a hybrid interaction cycle: click → API → text in history → new model decision → new tool call → updated UI.
The sequence looks roughly like this:
- The user clicks a button in your widget.
- The widget calls the API window.openai.sendFollowUpMessage or, in the React layer, the convenient wrapper hook useSendMessage (we’ll use the hook in the examples below). The argument is a plain string: the text as if typed by the user.
- ChatGPT adds this message to history as a new user_message.
- The model performs a new pass: it considers the entire history, including the previous toolOutput, and decides whether to call a tool, which one, and with what arguments.
- Your backend/MCP executes the tool call and returns the toolOutput.
- ChatGPT displays the new response: possibly the widget again (with new data), possibly text, or a combination.
Importantly, you almost never want to call the same tool directly from the widget, bypassing this cycle. Otherwise, the model “loses” a step: the history doesn’t contain the request that initiated the repeat call. Later, in longer scenarios, this leads to confusing context.
We can boil this down to a short formula:
User Click → useSendMessage("...") → ChatGPT (LLM) → Tool Call → New toolOutput → Widget rerender
4. Types of follow‑ups and who comes up with them
We’ve covered what happens under the hood after a follow‑up click. Now let’s see which kinds of follow‑ups make sense to offer and what roles they can play in a scenario.
In a real app, there are several “families” of follow‑ups. I group them along different axes: static vs. dynamic, by role (drill‑down, pivot, commit, etc.).
A table is more practical.
| Type | What it does | Example text/button |
|---|---|---|
| Suggestive | Helps when the user doesn’t know what to ask next | “Show more ideas”, “Narrow by interests” |
| Drill-down / Parametric | Narrows the previous request by parameters | “Cheaper”, “Only digital gifts”, “Only Nike” |
| Pivot | Changes the scenario branch | “Start over”, “Show gifts for a child” |
| Navigation | Moves to a different step in the process | “Proceed to checkout”, “Back to selection” |
| Commit | Confirms an action | “Order this gift”, “Save selection” |
From the perspective of idea sources, there are two big groups.
First, follow‑ups proposed by the app itself. These are our buttons in the widget tied to UI logic and data: for example, “Show similar to [Gift name]” or “Filter by interest: travel only.” Such suggestions can be hard-coded (static) or generated dynamically based on the toolOutput (dynamic).
Second, ChatGPT’s “native” suggestions — small chips under a message that the model invents on its own. You don’t control them directly; treat them as a free bonus, not a reliable mechanism. Your app should work even without them.
In this lecture, we’re more interested in the first group: buttons and suggestions that you render in your React component and, on click, send via sendFollowUpMessage.
5. Implementing follow‑ups in React: GiftGenius as an example
Let’s continue with our hypothetical GiftGenius app that finds gift ideas. After calling the get_gift_ideas tool, the widget receives a toolOutput with a list of gifts. In previous topics, we already built a card grid. Now let’s add a follow‑ups section.
Assume our SDK has hooks useWidgetProps and useSendMessage. The names are illustrative, but the concept matches the reference implementation:
import { useWidgetProps, useSendMessage } from '@/openai-apps';
export const GiftSuggestions: React.FC = () => {
const { toolOutput } = useWidgetProps();
const sendMessage = useSendMessage();
const gifts = toolOutput?.data?.gifts ?? [];
if (gifts.length === 0) {
return <div>No gifts found. Try changing your query.</div>;
}
const handleCheaper = () => {
sendMessage('Show cheaper gifts, up to $50');
};
const handleDigital = () => {
sendMessage('Show only digital gifts: gift cards, subscriptions, etc.');
};
return (
<div className="flex flex-col gap-4">
<div className="grid grid-cols-2 gap-2">
{gifts.map((gift: any) => (
<GiftCard key={gift.id} item={gift} />
))}
</div>
<div className="border-t pt-3 text-sm">
<div className="text-xs text-gray-500 mb-2">What to do next?</div>
<div className="flex flex-wrap gap-2">
<button
onClick={handleCheaper}
className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
>
Cheaper
</button>
<button
onClick={handleDigital}
className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
>
Digital only
</button>
</div>
</div>
</div>
);
};
Several points matter here.
First, sendMessage receives a string — that text will appear in the chat as if the user typed it. Your app should not mimic some “internal” system protocol; just phrase sentences so the model understands them.
Second, the follow‑ups section is visually separated (a top border, small “What to do next?” text) so the user understands this is more like continuing the dialog than elements of the card itself.
Third, there are only a few buttons — two. UX guidance suggests keeping roughly two to four options: enough to help but not overwhelm.
Dynamic follow‑up based on data
Suppose you want to let the user dive into a specific gift: “Show similar to this one.” Then the follow‑up text should mention the relevant object, and you can generate the text on the fly.
const handleShowSimilar = (giftTitle: string) => {
sendMessage(
`Show similar gifts to "${giftTitle}", either in the same budget or slightly more expensive`
);
};
And in the card:
<button
onClick={() => handleShowSimilar(gift.title)}
className="mt-2 text-xs text-blue-600 underline"
>
Show similar
</button>
This gives you a dynamic follow‑up tied to specific data from the toolOutput. These are the kinds of buttons that make the dialog “smart,” not just a set of generic “Next / Back.”
6. Why we send text instead of calling a tool directly
A typical frontend thought: “If I have useCallTool, why send text? I can call get_gift_ideas again with different parameters.” Sometimes you really do need that (we’ll cover more in the module about calling tools from a widget), but by default it’s better to go through a textual follow‑up. The reasons are practical.
First, the chat history stays coherent. From the outside, it looks as if the user wrote “Show cheaper gifts.” A week later, they can reopen the history and understand what happened. If you did everything through direct tool calls, different widgets would suddenly appear between user messages for no visible reason.
Second, the model can make additional decisions. For example, it may realize that instead of calling the same tool again, it should clarify the budget first: “Are you sure you want to lower the budget to $5? Maybe keep at least $20?” These flexible scenarios become impossible if you hard-code “click → the same tool with different arguments.”
Third, the model may call a completely different tool. Suppose the user clicks “Contact support,” and your system prompt teaches the model that it should call create_support_ticket rather than get_gift_ideas. A text follow‑up gives the model the freedom to switch tools.
So the practical rule of module 3: on UI click, in most cases send a textual follow‑up, not a direct tool call. Leave direct tool calls from the widget for specific cases when you definitely don’t need a new user step in history.
7. Tying follow‑ups to state: keeping UI and text in sync
An interesting problem: the UI “lives” its own life, and the chat text its own. Suppose you change a filter inside the widget on click and also send a follow‑up. If you updated only the UI but didn’t persist the new state via widgetState, on the next render ChatGPT will restore the old widgetState. As a result, the widget again shows the old filters, even though the chat history already has the “cheaper” step. That’s a strange experience.
So a good pattern is: when clicking a follow‑up, simultaneously:
- update the widgetState,
- send the follow‑up message.
Example:
import { useWidgetState, useSendMessage } from '@/openai-apps';
type GiftWidgetState = {
priceFilter?: 'any' | 'cheap' | 'premium';
};
export const GiftFollowups: React.FC = () => {
const [widgetState, setWidgetState] = useWidgetState<GiftWidgetState>();
const sendMessage = useSendMessage();
const handleCheaper = () => {
setWidgetState({ ...widgetState, priceFilter: 'cheap' });
sendMessage('Show cheaper gifts, around $50');
};
// ...
};
Now both ChatGPT and your UI know the filter changed. If the model rebuilds the widget later, it will see the updated widgetState and can, for example, form a new pre‑text like “Here are ideas in a more budget‑friendly segment.”
8. Designing good follow‑ups
A good follow‑up is half of UX success. It’s not just “pretty”; it saves the user’s mental energy.
There are a few practical principles worth taping above your monitor.
First, brevity. A follow‑up isn’t the place for a poem. One short phrase, understandable without UI context, is usually ideal: “Cheaper,” “Premium only,” “Change recipient.” If you need something longer, consider whether it should be normal GPT text instead of a button.
Second, action-oriented. Phrase it so it’s clear what will happen. “More ideas” is okay. “More about item 2” is better replaced with “Tell me more about the second option” (so the model understands what it’s about, even without UI).
Third, continuation rather than repetition. Instead of “Pick a gift again,” prefer “Change budget” or “Change the recipient’s hobby.” A follow‑up should move the user forward or sideways, not reset them to the starting point without reason.
Fourth, limited in number. Two to four buttons at the bottom of a widget are almost always enough. A plate of ten options turns into a fate-choosing exam: the user gets lost and clicks nothing.
Finally, mind the tone. If the entire app speaks in a friendly voice, it’s odd to drop a “CONFIRM ORDER” button in all caps. A follow‑up is part of the same dialog as the model’s text; the style should match.
9. “Dialogs around the widget” overall: who owns what
Don’t think of the widget as the “main character” and ChatGPT as the “frame” around it. It’s the opposite: the model continues to lead the dialog, and the widget is just one way to display and adjust data.
A typical scenario for GiftGenius looks like this:
- User: “I need a gift for my sister in IT, up to $100.”
- Model: text pre‑text — explains it will open GiftGenius and what it does.
- Widget: shows a set of ideas, provides follow‑ups.
- User: either writes a message or clicks a follow‑up button (for example, “Show only digital gifts”).
- Model: interprets it as text, calls the right tool, and, if needed, comments on the result (post‑text) or shows the widget again.
- And so on until the task is done — up to confirming the choice, ordering, etc.
Follow‑ups are the glue that binds each loop of this cycle. Without them, the user is left hanging after the widget: everything looks nice, but “what next” is unclear.
In more complex scenarios (workflows, agents), follow‑ups help model multi‑step funnels: “First choose the recipient,” “Now refine the budget,” “Now confirm the choice.” But for this module it’s enough to see that even the simplest single‑step app benefits greatly from a couple of well‑designed suggestions.
10. Practice: what to do right now
A good reinforcement exercise is to improve your current learning widget.
If you already have a list of results (for example, gifts, hotels, or documents), add a small “What’s next?” block under it with two or three buttons. Try to match the types from the table above: one should be drill‑down (e.g., “Cheaper”), another a pivot (“Change recipient”), and a third, if needed, navigational (“Proceed to checkout”).
Inside the click handlers, call useSendMessage with meaningful natural‑language text, and don’t forget to update the widgetState if necessary. Then rerun the scenario in ChatGPT and see how the dialog feels: is it clearer what to do after the widget?
Also try intentionally making bad follow‑ups: long, vague, with a dozen options — and compare the feeling. It’s a quick way to experience the difference yourself.
11. Common mistakes when working with follow‑ups
Error #1: The “silent” widget.
A developer builds a great UI but provides no follow‑ups at all. The user sees cards, thinks “nice,” and must guess that they can ask, for example: “Show cheaper” or “Change recipient.” Most people won’t guess and will leave. At least one or two “what’s next” hints under the widget fix this problem.
Error #2: Too many buttons.
The opposite extreme is spamming the user with a dozen options: “Change budget,” “Change interests,” “Switch currency,” “Save selection,” “Share with a friend,” “Show similar,” “Ask support,” and so on. You get a psychological buffet where choosing is hard. Start with the two or three most common actions and leave the rest to the model and normal text.
Error #3: Dialog logic only in the frontend.
Sometimes people try to “optimize” and, instead of sending a follow‑up via useSendMessage (or low‑level sendFollowUpMessage), they directly call the same tool from the widget and update the UI. The chat history then says nothing about what actually happened. After a couple of steps, the model starts to get confused — and so do you. The right path is to keep dialog logic at the text and tools layer, with the widget as a thin UI layer.
Error #4: Unclear or ambiguous wording.
A “More” button without context can mean anything: more gifts, more text, more money? Similarly, phrasing like “Recalculate” or “Rebuild” is unclear to both the model and the user. The best follow‑ups are specific: “Show more options in this budget,” “Show only digital gifts.”
Error #5: Unsynced UI and text.
A classic: on “Cheaper” click, you updated a UI filter but didn’t send a follow‑up to the chat or didn’t update the widgetState. As a result, there’s no step about the budget change in history, and on the next widget render the filter “snaps back.” It feels “broken.” Use the setWidgetState + sendMessage combo so text and UI stay in lockstep.
Error #6: Trying to control ChatGPT’s “native” suggestions.
Developers sometimes expect ChatGPT to generate the right follow‑up chips under a message and skip adding their own. But those suggestions aren’t guaranteed and aren’t controlled by your app. Treat them as a nice bonus, but always provide your own critical follow‑up buttons in the widget.
GO TO FULL VERSION