Rich messages
A rich message (Bot API 10.3) is one message with headings, lists, task lists, tables, quotes, collapsible details, code, formulas, footnotes, pictures, videos, maps and buttons inside the text. Where a text page needs several messages or a picture plus a caption, a rich page needs one. Return rich instead of text:
const report = page('report').render(({ nav }) => ({
rich: [
'# Weekly report',
'',
'| Metric | Value |',
'|:-------|------:|',
'| Orders | **128** |',
'| Refunds | 3 |',
'',
'- [x] Ship the new checkout',
'- [ ] Fix the search',
],
keyboard: [[nav.button('Details', details)], [nav.back()]],
}));rich takes three forms:
a string, md fragments, or an array of them (joined with newlines) | Telegram's Rich Markdown, GitHub-style: # headings, **bold**, *italic*, ~~strike~~, ==marked==, ||spoiler||, `code`, fenced code, > quotes, lists, - [ ] tasks, tables, ---, $x^2$ and $$…$$ formulas, [^1] footnotes, , and HTML tags for the rest |
one html fragment | Rich HTML: <h1>, <p>, <ul>, <table>, <details>, <blockquote>, <figure>, <tg-map>, <tg-button>, … |
{ markdown }, { html } or { blocks } | the Bot API's InputRichMessage as it is, with media, is_rtl or skip_entity_detection |
Telegram's rich message formatting lists every tag. Separate paragraphs with an empty line, as in any Markdown.
User input
As with text, the md template escapes what you interpolate, for Rich Markdown too: a name like # Bob | *x* can't become a heading, a table column, a formula or a link. A table from data stays a table:
rich: [
'| Plan | Price |',
'|---|--:|',
...plans.map((p) => md`| ${p.name} | ${p.price} |`),
],escapeRichMarkdown(text) does the same for strings you build yourself, and html escapes for Rich HTML. Translations work too: t.md(key, vars).
Delivery
- A rich page edits a text or rich message in place, and a text page edits a rich message in place. Between rich and media messages (photos, videos…) the pressed message is replaced by a new one: Telegram can't edit one into the other.
keyboardworks as on any page. Buttons can also go inside the text:<tg-button type="callback_data" data="${nav.data(page)}">Open</tg-button>(or abuttonsblock) opens a page likenav.button.- Ephemeral pages,
sendTo,app.editand inline results can be rich. Inline results can't upload files: their media must already be on Telegram. - A rich message holds up to 32768 characters and 50 media; it isn't split like long text.
richcan't be combined withtextor media fields, since the media goes inside the message. app.stream(…, { rich: true })streams an AI answer as a rich draft and sends it as a rich message.
Try it
examples/rich.ts is a handbook with a table, collapsible answers, a button inside the text and a page built from blocks.
The simulator shows rich messages and checks them like Telegram, closely but not with Telegram's own parser: test on Telegram before relying on an unusual construct.