Telegram Markdown: MarkdownV2 Syntax and Escaping Guide

September 11, 2026 · 9 min read

Telegram Markdown: Format Messages with MarkdownV2

Telegram markdown comes in three dialects, and most "not working" problems come from mixing them up. In the app you type **bold** and __italic__. In the Bot API's MarkdownV2 mode you send *bold* and _italic_ and escape 18 reserved characters. Since June 2026, bots can also send Rich Markdown with headings and tables. This guide covers all three.

Which Telegram Markdown Are You Using?

The confusion starts because Telegram never parses Markdown on the server for regular users. The app converts a few typed markers into formatting entities when you hit send, while bots send text plus a parse_mode that tells the API how to read it. The two syntaxes overlap just enough to look identical and differ just enough to fail.

Where you writeBoldItalicStrikethroughSpoilerCodeHeadings and tables
Telegram app (typing)**bold**__italic__~~text~~||text||`code` and ```No
Bot API, parse_mode: MarkdownV2*bold*_italic_~text~||text||SameNo
Bot API, sendRichMessage (Rich Markdown)**bold***italic* or _italic_~~text~~||text||SameYes

If you're a regular user, read the next section and skip the escaping. If you're writing a bot, jump to the MarkdownV2 section, then decide whether the newer rich message mode fits your project.

Telegram Markdown in the App: What You Can Type

Telegram Desktop's own source defines the typed markers it converts. The input field code lists ** for bold, __ for italic, ~~ for strikethrough, and || for spoiler. It also lists a single backtick for inline code, three backticks for a code block, and > for a quote. Underline has no typed marker; use the formatting menu instead.

**Release 2.4 is out**
__Rolling out over the next hour__
~~Old build 2.3~~
||The next version adds folders||
Run `telegram --version` to check
> Report bugs in the pinned thread

Type that into a chat and send it. The markers disappear and the formatting stays. In our experience the iOS and Android apps convert the bold, italic, strikethrough, spoiler, and code markers the same way.

Two things don't work. Lists render as the literal - or 1. you typed, and headings never exist in chat messages, so # Title stays a hash sign. For a list, most people just use an emoji or dash per line and accept the plain text. Bold on its own line makes a decent section title, and a blank line between paragraphs is preserved, so a longer message can still look organised.

If a marker refuses to convert, check for a space between the marker and the word. ** bold ** with inner spaces stays literal, while **bold** converts. The same applies to underscores and tildes.

Links are the awkward case. The app turns a selected text into a link through the formatting menu (Ctrl+K on desktop), and Telegram Desktop also recognises [text](https://example.com) typed inline, thanks to its link-validation code. Reports differ on whether the mobile apps convert that inline form, so select-and-format is the reliable route. Our Markdown links guide explains the general syntax if it's new to you.

What Is Telegram MarkdownV2?

MarkdownV2 is the Bot API formatting mode that supports every message entity, including underline, strikethrough, spoiler, and block quotes. You enable it by passing parse_mode: "MarkdownV2" with sendMessage. The older Markdown mode is kept for backward compatibility. The Bot API docs say it can't express underline, strikethrough, spoilers, or quotes and can't nest entities. Use V2 for anything new.

*bold* _italic_ __underline__ ~strikethrough~ ||spoiler||
*bold _italic bold ~italic bold strikethrough~ bold*
[inline URL](https://example\.com/)
[mention a user](tg://user?id=123456789)
`inline code`
```python
print("code block with a language")
```
>Quoted line
>Second quoted line
**>Expandable quote starts here
>Hidden until tapped||

Bold uses one asterisk here, not two. Italic uses one underscore, underline uses two. The block quote and expandable block quote arrived in Bot API 7.0 (December 2023) and Bot API 7.4 (May 2024) according to the changelog.

Why Does a Telegram Markdown Bot Message Fail to Send?

Escaping. The docs list 18 characters that need a backslash everywhere outside code and link URLs. They are _, *, [, ], (, ), ~, `, >, #, +, -, =, |, {, }, ., and !. Miss one and the API returns a 400 error such as "Can't parse entities: character '.' is reserved and must be escaped".

The period and the hyphen catch everyone, because normal sentences are full of them. This message fails.

Build 2.4 finished - see https://example.com/log!

This one sends.

Build 2\.4 finished \- see https://example\.com/log\!

Inside code spans and code blocks, only the backtick and backslash need escaping. Inside the URL part of a link, only ) and \. Everywhere else, the full list applies.

The sane approach is to escape programmatically and only add markup after escaping the user-supplied parts. Here is the Python version.

import re

def escape_md2(text: str) -> str:
    return re.sub(r'([_*\[\]()~`>#+\-=|{}.!\\])', r'\\\1', text)

body = escape_md2("Build 2.4 finished - see log!")
message = f"*Deploy*\n{body}"

The JavaScript version is the same regular expression.

const escapeMd2 = (text) =>
  text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, (ch) => "\\" + ch);

const message = `*Deploy*\n${escapeMd2("Build 2.4 finished - see log!")}`;

If you'd rather convert normal Markdown than hand-write V2, the telegramify-markdown package for Python turns standard Markdown into Telegram entities, and telegram-markdown-v2 on npm does the conversion for JavaScript. In our experience the escape-first approach is easier to debug when a message is rejected, because the reserved character is always in the unescaped part.

Rich Markdown for Bots (Bot API 10.1 and Later)

On June 11, 2026, Telegram released Bot API 10.1 with rich messages. Bots call sendRichMessage and pass content in a markdown field, and the docs describe the syntax as compatible with GitHub Flavored Markdown where possible. That means the standard syntax you'd write anywhere else: **bold**, headings one through six, fenced code, ordered and task lists, tables with alignment colons, footnotes, and $ math.

Rich messages don't use the MarkdownV2 escape list. The message limits are 32,768 characters, 500 blocks, and 20 table columns. Table cells can only contain inline formatting, and Markdown isn't parsed inside most block-level HTML tags, so keep the structure to plain Markdown blocks. Telegram's June 2026 announcement also added native .md file rendering in the in-app browser, so a shared Markdown file now opens formatted rather than as plain text.

We prefer rich messages for anything with structure, like reports and changelogs, and MarkdownV2 for short one-line alerts. One limitation: rich messages are a bot-only feature, so a regular user typing into a chat still gets the app's small marker set.

Draft the structured version in the editor below. It uses GitHub-flavoured syntax, so what renders here is what a rich message accepts. If you're targeting MarkdownV2 instead, strip the headings and table, then run the escape function.

Deploy report

Build 2.4 finished in 3 minutes.

Check Result
Tests pass
Lint pass
  • Tagged release
  • Notify support

Standard Markdown like this works in sendRichMessage. For MarkdownV2, keep only bold, italic, code, and links, then escape the reserved characters.

56 words319 characters13 lines
Markdown

How to Use a Telegram Markdown Code Block

Code blocks are the same in all three dialects. Open with three backticks on their own line, add an optional language name, paste the code, and close with three backticks. Recent Telegram apps use the language name for syntax highlighting.

```python
def ping():
    return "pong"
```

Inside a MarkdownV2 code block you still have to escape backticks and backslashes, but nothing else. That makes code blocks a handy place to put text you don't want to escape. They're also the usual workaround for a table in a chat message: pad the columns with spaces inside a code block so the monospace font lines them up. Our Markdown code block guide covers the fence syntax in more depth.

Inline code uses a single backtick on each side and works while typing in the app, in MarkdownV2, and in rich messages. For another platform, our Discord Markdown cheat sheet is the sister post. The Reddit formatting guide covers a flavour that does support headings and tables.

Common Telegram Markdown Mistakes

Double asterisks in a bot message. **bold** under MarkdownV2 parses as two bold entities with nothing inside, or errors out. Use a single asterisk, or switch to rich messages where the double form is correct.

Unescaped periods and hyphens. "v2.4" and "step-by-step" both reject in MarkdownV2. Run every dynamic string through an escape function before wrapping it in markup.

Expecting headings and lists in chat. Neither the app nor MarkdownV2 supports them. Use emoji or dashes as visual bullets, bold for section titles, and reach for sendRichMessage when a bot needs real structure.

Telegram Markdown FAQ

Telegram markdown is three syntaxes sharing one name. Users type markers in the app, bots escape MarkdownV2, and bots on API 10.1 or later can send GitHub-style Rich Markdown. Match the dialect to where the message is sent and most errors disappear. Draft anything longer than a line in the editor to check the structure, then convert or escape it for Telegram.