Telegram MarkdownV2 escaping: the complete reference

MarkdownV2 is the strictest of Telegram's three parse modes. It supports the most formatting, but any reserved character used as plain text must be escaped with a backslash — or the Bot API rejects the message with 400: can't parse entities.

The 18 reserved characters

Escape each of these with a preceding backslash in regular text:

_ * [ ] ( ) ~ ` > # + - = | { } . !

Frequent offenders in real messages: . (every sentence), - (lists, ranges, compound words), !, # (issue numbers), + (phone numbers), ( ) (asides like this one).

Formatting syntax

StyleMarkdownV2 syntax
Bold*bold text*
Italic_italic text_
Underline__underline__
Strikethrough~strikethrough~
Spoiler||spoiler||
Inline code`inline code`
Code block```python code here ```
Link[label](https://example.com/)
User mention[name](tg://user?id=123456789)
Custom emoji![👍](tg://emoji?id=5368324170671202286)
Block quote> quoted line
Expandable quote**> hidden until expanded||

Entities can nest (bold inside italic, spoiler around links), but code and pre cannot contain other entities. Ambiguity trap: italic followed immediately by underline needs an empty separator — _italic_\r__underline__.

Context-specific rules

  • Regular text: escape all 18 characters above.
  • Inside `code` and ```pre```: escape only ` and \.
  • Inside link/emoji URLs (the part in parentheses): escape only ) and \.

This context sensitivity is why one global escape function is not enough — it would corrupt your code blocks and URLs.

Escape functions

For plain-text segments only:

JavaScript

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

const escapeCode = (text) => text.replace(/[`\\]/g, "\\$&");
const escapeUrl = (url) => url.replace(/[)\\]/g, "\\$&");

Python

import re

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

# python-telegram-bot ships one too:
# from telegram.helpers import escape_markdown
# escape_markdown(text, version=2)

Or skip all of this

If your source text is standard Markdown (README files, LLM output, CMS content), converting beats escaping: telegramify-markdown in code, or the free md2tg converter in the browser or via HTTP from n8n and other automation tools.

Related guides

Skip the manual escaping

Paste your Markdown into the free md2tg converter and get valid Telegram MarkdownV2 out — every reserved character escaped for you.