Open experience UI from chat buttons
Existing sendChoices buttons and sendKeyboard options can open installed
native experience pages or bottom sheets. Use a build of the SDK containing
ExperienceButtonAction and an Atlas client supporting these actions. Experiences
are always available; no developer setting is required.
Choose the right action
| Where the button appears | Action shape | Result |
|---|---|---|
| Chat message or reply keyboard | { type: 'experience.page', experienceId, target } |
Open a full-screen page |
| Chat message or reply keyboard | { type: 'experience.sheet', experienceId, target } |
Open a medium/large bottom sheet |
| Inside an experience | openPage(target, options?) |
Navigate within the mounted experience |
| Inside an experience | openSheet(target) |
Present a sheet over the current experience screen |
The openPage and openSheet helpers return UIAction objects with tool and
args; they are not chat-button actions. Chat buttons use
ExperienceButtonAction with type, experienceId, and target.
Complete TypeScript example
This bot shares a mock food-ordering pack with /install-food. After reviewing
and installing that pack, the user sends /food to see the existing chat
buttons. The example collects local demo choices and never submits a real order.
import {
createAtlasBot,
type ChoiceRows,
type ExperienceButtonAction,
} from 'atlas-bot-sdk';
import {
page, screen, ui, scope, openSheet, closeSheet, closePage, sendExperience,
type ExperienceDefinition,
} from 'atlas-bot-sdk/experiences';
const experience: ExperienceDefinition = {
id: 'food-chat-demo',
title: 'Food ordering demo',
version: '1.0.0',
icon: 'takeoutbag.and.cup.and.straw',
tools: ['ui.open_sheet', 'ui.close_sheet', 'ui.close_page'],
permissions: [],
properties: {
address: { type: 'string', title: 'Delivery address', default: '123 Market Street' },
},
ui: screen({ title: 'Food ordering demo' }, [
ui('Text', { text: 'Mock food ordering. Open the menu from the chat.' }),
page('menu', { title: 'Tonight’s menu', subtitle: 'Deliver to {{address}}' }, [
ui('ItemRow', { title: 'Chicken bowl', subtitle: 'Roasted vegetables and rice', text: '$14' }),
ui('SummaryRow', { title: 'Delivery address', text: '{{address}}' }),
], [
{ title: 'Edit address', action: openSheet('address') },
{ title: 'Back to chat', action: closePage() },
]),
ui('Sheet', {
id: 'address', title: 'Delivery address',
elements: [ui('Control', { scope: scope('address') })],
buttons: [{ title: 'Done', action: closeSheet(), primary: true }],
}),
]),
};
const menuAction: ExperienceButtonAction = {
type: 'experience.page', experienceId: experience.id, target: 'menu',
};
const buttons: ChoiceRows = [
[{ label: 'Browse menu', value: '/food-help', action: menuAction }],
[{
label: 'Delivery address', value: '/food-help',
action: { type: 'experience.sheet', experienceId: experience.id, target: 'address' },
}],
];
const bot = createAtlasBot({ accessToken: process.env.ATLAS_BOT_TOKEN, llm: false });
bot.onCommand('install-food', ctx => sendExperience(ctx, experience));
bot.onCommand('food', ctx => ctx.sendChoices('What would you like to do?', buttons));
bot.onCommand('food-help', ctx => ctx.sendText(
'Install the pack with /install-food, then use an Atlas client supporting experience chat buttons.'
));
bot.start();
Sharing a pack only sends its install card. The bot does not receive an installation acknowledgement from a navigation-button tap. Offer installation and navigation as separate steps; do not assume sending a pack installs it.
Field reference
| Field | Meaning |
|---|---|
label |
Visible button text |
action.type |
Exactly experience.page or experience.sheet |
action.experienceId |
Installed manifest ID, such as food-chat-demo; not a workflow ID |
action.target |
Declared Page or Sheet element ID, such as menu or address |
value |
Optional fallback reply for older clients; defaults to the label |
disabled |
Freeze one button; the message widget can also be disabled |
row |
Optional row index; nested option arrays usually make grouping clearer |
silent |
Applies to callback buttons; action takes precedence when both are supplied |
ChoiceInput describes one string or button object. ChoiceRows accepts a flat
list or nested rows. ExperienceButtonAction describes the destination. All
three are type-only exports from atlas-bot-sdk; the corresponding wire types
are also available from atlas-bot-sdk/wire.
Targets resolve in the installed pack's primary UI definition, the same one
opened by its launcher. The SDK builder creates exp.<experienceId>.app.
Hand-authored packs with several UI definitions use the lexicographically first
namespaced definition as their primary definition; this payload does not select
an alternate definition. Declare entry pages and sheets in that primary UI.
There is no implicit home target: the requested element must exist.
Navigation and state
- A page opens full-screen with native Close and page navigation. A bottom sheet uses medium/large system detents and a drag indicator.
- The originating chat remains mounted, preserving its draft and selected workflow. Closing the destination returns there.
- State is hydrated for the experience in the originating stream before the form mounts. Fields and subsequent pages share the form's state.
- In a chat-launched page,
closePage()at the entry screen closes back to chat. In a chat-launched sheet,closeSheet()closes back to chat. A nested sheet closes back to its parent experience screen. - Inside a wizard,
openPage(id, { mode: 'root' })clears completed history;replacereplaces the current step. Declare the navigation tools in the pack's grants. Normal form discard protection applies. - Navigation does not post a chat reply, invoke a silent callback, or mark the button answered. It can be tapped again. Reply keyboards stay available.
- Changing chats or uninstalling the experience dismisses the destination.
Existing editMessage(chatId, eventId, text, { choices, disabled }) accepts the
same options. Use it to change a target or freeze a button. Atlas checks that
the tapped option still belongs to the current, enabled message before opening.
Troubleshooting and compatibility
| Symptom | Check |
|---|---|
| “Unable to open experience” | Install/update the pack; verify the manifest ID and target in its primary UI |
| Page exists but will not open | Match experience.page to a Page, or experience.sheet to a Sheet; native mobile_app UI is required |
| A reply appears instead | Older clients may ignore action and send value; handle the fallback command in the bot |
| Bot handler never runs on tap | Navigation is local; use ordinary replies or silent: true without action when a bot callback is required |
| Button does nothing | Check per-button/widget disabling, an already-answered message, or an edited/removed option |
Unknown or malformed non-null actions stay unavailable on supporting clients; they do not become outgoing replies. Canvas/web experiences and arbitrary URLs are not supported by these destination actions. A tap does not expand tool permissions; pack installation and existing grants still apply.
See the native UI library reference, ride-booking example, and native example packs.