Telegram Message Formatting
Telegram message formatting is the set of rules a bot follows to make text bold, italic, monospaced or linked. There are three ways to express it: MarkdownV2, HTML, and a plain string paired with a list of entity ranges. The API accepts one per message. MarkdownV2 is the one most bots use, and the one that fails most often. It reserves eighteen characters, and four of them turn up in ordinary sentences: the full stop, the hyphen, the plus and the exclamation mark. A sentence as plain as Order #123 is ready! is already invalid, and Telegram answers can’t parse entities rather than explaining. We ran into this often enough building our own bots that we wrote the fix once and gave it away. Paste what you have, whether that’s plain text, Markdown, or the answer a language model produced. You get back something the API accepts, in whichever of the three modes you send.
- Escaping that knows where it is. Reserved characters are escaped by context, so the dots in
pip install x==1.0survive inside a code block. They don’t arrive full of backslashes. - Splitting that closes what it opens. Past 4,096 characters the message is cut on a boundary outside every entity. The formatting reopens in the next part.
- Nothing leaves the tab. No upload, no account, and we never ask for your bot token. Formatting text doesn’t need one.
What this does that the others do not
- Correct UTF-16 offsets
- The API counts entity positions in UTF-16 code units. Python counts code points. So one emoji shifts every later entity by one, and the message gets rejected. A JavaScript string index is already the right unit, so these numbers come out correct with no conversion step.
- Entity-aware splitting
- A plain
slice(0, 4096)cuts through the middle of a bold run. The API then answers Can’t find end of Bold entity. Here the break is chosen to fall outside every entity. - Error decoding
- Telegram reports the fault at a UTF-8 byte offset. That matches nothing your editor shows you. Paste the error, get the line, the column and the character.
All three are covered by tests that run on every commit, built from the examples in Telegram’s own documentation.
Format a Telegram message
Paste your text or Markdown. Everything runs in this tab — nothing is uploaded, nothing is stored.
Telegram accepts none of the document syntax below. It is being converted rather than escaped, so no stray ## or |---| ends up in the message.
- line 1 — ATX heading (#) — Telegram has no headings
- line 3 — **double asterisks** — MarkdownV2 uses a single one
- line 5 — Bullet list — Telegram has no list markup
- line 7 — Markdown link
- line 9 — Markdown table — Telegram has no tables in text messages
How it will look
Rendered the way a Telegram client renders it. Spoilers stay hidden until you tap them. Reading your input as Markdown.
IN-8842-XZ
• Delivery: 2-4 days
• Support: chat with us
Item Qty
──── ───
Lamp 2
Reply STOP to unsubscribe.What to send
Converted from your Markdown.
The mode most bots use. Every reserved character is escaped by context, so code blocks stay readable.
*Order \#1284 is ready\!*
Hi *Priya*, your order shipped today\.
• Tracking: `IN-8842-XZ`
• Delivery: 2\-4 days
• Support: [chat with us](https://t.me/yourbot)
```
Item Qty
──── ───
Lamp 2
```
>Reply STOP to unsubscribe\.Send it
The string literal is escaped for the language, so it survives the paste.
from telegram import Bot
from telegram.constants import ParseMode
bot = Bot(token=BOT_TOKEN)
await bot.send_message(
chat_id="@yourchannel",
text="*Order \\#1284 is ready\\!*\n\nHi *Priya*, your order shipped today\\.\n\n• Tracking: `IN-8842-XZ`\n• Delivery: 2\\-4 days\n• Support: [chat with us](https://t.me/yourbot)\n\n```\nItem Qty\n──── ───\nLamp 2\n```\n\n>Reply STOP to unsubscribe\\.",
parse_mode=ParseMode.MARKDOWN_V2,
)Decode an API error
Paste what Telegram answered. The offset in those errors counts UTF-8 bytes, which is why it never matches what your editor shows — this converts it back to a line and a column in the MarkdownV2 output above.
The conversion and escaping run entirely in this tab, on the same code and the same 137 tests the rest of our tooling is built on. Nothing you type is sent anywhere.
Formatting text in the Telegram app
If you are writing a message by hand rather than sending one from a bot, you do not need any of the escaping below. Select the text and pick a style. On mobile, tap and hold to select, then choose from the pop-up. On desktop, select and use the right-click menu, or a shortcut: Ctrl+B for bold, Ctrl+Shift+M for monospace. Telegram text formatting covers eight styles and no more: Telegram bold text, italic, underline, strikethrough, spoiler, monospace, code block and quotation. Typing the characters works too: *bold*, _italic_, ~strikethrough~, ||spoiler|| and `code`.
A bot can’t press a toolbar button. That’s the whole reason this page exists. It has to describe the formatting inside the message it sends, as MarkdownV2, as HTML, or as a list of entity ranges. Each of those carries rules a person typing into the app never meets. One more thing, plainly: Telegram has no font picker. The “fancy font” generators you may have found swap in lookalike Unicode characters instead of changing any font. They break screen readers, they often fail to render on other devices, and search reads the result as gibberish. Those eight styles are the whole set.
Telegram markdown tables, lists and code blocks
An ordinary Telegram message has no list markup and no table markup. So anything that claims to send one is picking a rendering on your behalf. This tool makes that choice explicit. Bullets become the • character. Numbered items keep their digits and get the escaped full stop MarkdownV2 requires. A table becomes a single pre block with padded columns and a rule under the header. Monospace is the only way alignment survives in a chat client. Code blocks are the one construct that maps directly: three backticks with an optional language name, which the app renders monospaced with its own copy button. Rich messages, added in Bot API 10.1 on 11 June 2026, finally have real list and table blocks. If you’re targeting those, the rich message builder handles them.
Why naive escaping breaks code blocks
Most converters do one pass over the string and put a backslash in front of every reserved character. That’s right for prose and wrong everywhere else. MarkdownV2 changes its rules depending on where you are. Inside a code or pre block only the backtick and the backslash are special. Inside the bracketed target of a link, only the closing parenthesis and the backslash are. Escape a code block with the prose rules and your user receives pip install python\-telegram\-bot==21\.0 — a command that no longer works when they copy it. This tool parses the message into ranges first, then decides each character’s escaping by the range that encloses it. That turns the question from a guess into arithmetic.
Sending a language model’s answer to a chat
Wiring a model into a bot has become ordinary, and it brings a formatting problem that didn’t exist before. Models answer in full GitHub-flavoured Markdown: second-level headings, nested bullet lists, tables, horizontal rules, occasionally an image. Telegram supports none of those in a text message. Escaping them is the wrong instinct. It makes the message valid and leaves \#\# and \|---\| visible in the chat. Converting is the right one. A heading becomes a bold line. Bullets become •, a numbered list keeps its numbers, and a table becomes a monospace block with padded columns so it still lines up. A horizontal rule gets removed, because there’s nothing it could map to. The tool spots that shape on paste and says so.
The mode that cannot fail to parse
If your messages keep getting rejected and the cause is never quite the same, stop escaping and send text plus entities. The text travels exactly as written, with the formatting described alongside it as a list of ranges. There’s no parse step to go wrong. The one hazard is the unit. Those ranges are UTF-16 code units, and a language that measures strings differently will produce numbers that look right and land in the wrong place. It’s the most common bug in hand-written entity code, and it only shows itself once someone sends an emoji.
Telegram message formatting — questions
- How do I format text in Telegram?
- In the app, select the text and choose a style. On mobile, tap and hold to select, then pick Bold, Italic, Monospace, Strikethrough, Underline or Spoiler from the pop-up. On desktop, select and use the right-click menu, or a shortcut such as Ctrl+B for bold and Ctrl+Shift+M for monospace. Typing the characters works too: *bold*, _italic_, ~strikethrough~, ||spoiler|| and `code`. A bot is a different matter. It can’t press a toolbar button, so it sends the formatting as MarkdownV2, HTML or entities. That is what the tool on this page produces.
- How do I change the font style on Telegram?
- Telegram has no font picker for messages. The "fancy font" tools you may have seen don’t change the font at all. They swap in lookalike Unicode characters, which break screen readers and often fail to render on other devices. What Telegram does offer is a fixed set of styles: bold, italic, underline, strikethrough, spoiler, monospace, code block and quotation. You apply those per message from the selection menu. They’re the same set a bot can send through the API.
- What is Telegram MarkdownV2?
- MarkdownV2 is the second and current Markdown parse mode of the Bot API, passed as parse_mode when a bot sends a message. It replaced the legacy Markdown mode, which couldn’t express underline, strikethrough, spoilers or quotations, and didn’t allow nesting. The trade for that power is escaping. MarkdownV2 reserves eighteen characters that each need a backslash, and the rules change inside code blocks and link targets. That escaping is what this tool does for you.
- How do I use ``` in Telegram?
- Three backticks open and close a code block, and a language name can follow the opening set: ```python on its own line, the code, then ``` on a line of its own. The block renders monospaced with a copy button in the app. Two things trip people up. Inside the block, backticks and backslashes still need escaping. Everything else must be left alone, so a converter that escapes dots and dashes inside code hands your reader a command that no longer runs. For a short fragment inside a sentence, single backticks give you inline `code` instead.
- Does Telegram support Markdown tables and lists?
- Not in an ordinary message. There is no list markup and no table markup, so a converter has to pick a rendering. Bullets become the • character, numbered items keep their digits, and a table becomes a monospace block with padded columns. That is the only way column alignment survives in a chat client. Bot API 10.1, released on 11 June 2026, changed this for rich messages, which do have real list and table blocks. The rich message builder on this site targets those.
- Which characters have to be escaped in Telegram MarkdownV2?
- Eighteen of them: _ * [ ] ( ) ~ ` > # + - = | { } . ! and each needs a preceding backslash. Four catch people out: the full stop, the hyphen, the plus and the exclamation mark. They turn up in ordinary sentences. "Order #123 is ready!" is already invalid MarkdownV2, and the API answers Bad Request: can't parse entities. The rules change by context, though. Inside a code or pre block, only the backtick and the backslash are escaped. Inside the bracketed part of a link, only the closing parenthesis and the backslash are. That is why a single find-and-replace over the whole message corrupts code blocks. It escapes dots and dashes that were meant to stay.
- Why does my bot answer "can't parse entities"?
- Almost always one of three things. A reserved character was left unescaped, so Telegram started reading markup where you meant text. A marker was opened and never closed, which produces "Can't find end of Bold entity". Or the message was cut at 4,096 characters through the middle of an entity, leaving the closing marker in a part that never got sent. Paste the error into the decoder above with your output in place. The byte offset Telegram reports counts UTF-8 bytes, so it never matches what your editor shows you. The decoder converts it back to a line and column you can act on.
- How do I send ChatGPT or Claude output to Telegram?
- Convert it rather than escape it. Language models answer in GitHub-flavoured Markdown: ## headings, bullet lists, tables, horizontal rules. Telegram supports none of that in a text message. Escaping leaves the hashes and pipes visible in the chat. Converting maps each construct onto something Telegram has. Headings become bold lines, bullets become •, ordered lists keep their numbers, and tables become a monospace block with padded columns. Horizontal rules get dropped, because there is nothing to map them to. Paste the answer above and the tool spots that shape and converts it.
- What is the difference between MarkdownV2, HTML and entities?
- They are three ways to describe the same formatting. MarkdownV2 is the most used and has the most escaping rules. HTML mode escapes only three characters, <, > and &, which makes it easier to generate from a template. text + entities sends the plain text untouched alongside a list of ranges. It has no escaping rules at all, so it cannot fail to parse. The catch is that those ranges count UTF-16 code units, which is where most hand-written implementations go wrong. If your messages keep getting rejected and you cannot find why, entities is the mode that ends the problem.
- Why are my entity offsets wrong when there is an emoji in the message?
- Because the Bot API counts offsets in UTF-16 code units and your language probably does not. Python's len() counts code points, so an emoji outside the basic multilingual plane, which is most of them, measures 1 there and 2 to Telegram. Every entity after that emoji is then off by one. The formatting lands on the wrong characters, or the API rejects the message outright. JavaScript string indices are UTF-16 already, so the offsets this tool produces are correct as they stand. Copy them. Don't recompute them on the other side.
- How long can a Telegram message be?
- 4,096 characters for a text message and 1,024 for a media caption, counted after entity parsing. That means the text your reader sees, not the escaped source you send. The distinction matters because escaping can nearly double the source. 200 full stops become 400 characters of MarkdownV2 but still count as 200. Rich messages, added in Bot API 10.1, raise the ceiling to 32,768. When a message does need splitting, the break has to fall outside every formatting entity, or the part ships with an unclosed marker.
- Does my text get uploaded anywhere?
- No. The parsing, escaping, conversion and preview all run in this browser tab, and the page makes no network request with your content. That is a design decision, not a promise. People paste real broadcast copy and real customer names into a formatter, and none of it has any reason to reach a server.
- Do you ever ask for my bot token?
- Never, on this page or any other. Formatting is a pure text transformation. It needs no credentials to do its job, so asking for one would mean collecting something we have no use for. If a formatting tool asks for your bot token, that is worth a second look. The token is full control of your bot.
Building the links your bot sends? The link generator builds t.me and tg:// pairs with start payloads. Working with Bot API 10.1 blocks instead? The rich message builder covers those. All of them are listed on the tools hub.