← Back to Blog

Telegram MarkdownV2 Special Characters — Complete Escape Guide

March 2026 · 7 min read

If you've ever built a Telegram bot and tried to send a formatted message using parse_mode: "MarkdownV2", you've probably seen this error: "Can't parse entities: Character '.' is reserved and must be escaped." This happens because MarkdownV2 has 18 special characters that must be escaped with a backslash whenever they appear outside of formatting markers.

This guide covers every special character, when and how to escape them, the exceptions you need to know, and code examples in Python and Node.js.

The 18 Special Characters

In all places outside of code blocks, inline code, and link URLs, these characters must be preceded by a backslash:

CharacterNameEscaped formCommon in
_Underscore\_Variable names
*Asterisk\*Math, emphasis
[Left bracket\[Arrays, links
]Right bracket\]Arrays, links
(Left paren\(Functions, URLs
)Right paren\)Functions, URLs
~Tilde\~Home paths
`Backtick\`Code snippets
>Greater than\>Quotes, arrows
#Hash\#Hashtags, IDs
+Plus\+Math, phone numbers
-Hyphen\-Dashes, subtraction
=Equals\=Assignment
|Pipe\|Tables, OR operator
{Left brace\{JSON, objects
}Right brace\}JSON, objects
.Period\.Every sentence
!Exclamation\!Emphasis, warnings

The Three Exceptions — When NOT to Escape

1. Inside inline code (backticks)

Content wrapped in single backticks `like this` is treated as raw text. Only backticks and backslashes need escaping inside code spans. All other special characters are displayed as-is.

`print("Hello, World!")` → displays as: print("Hello, World!)

2. Inside code blocks (triple backticks)

Same rule as inline code. Inside ```code blocks```, only backticks and backslashes need escaping. This is why showing JSON or code in a code block works without escaping every dot and brace.

3. Inside link URLs

In the URL part of [text](url) links, only ) and \ need escaping. The link text follows normal escape rules. The URL does not.

[Click here](https://example.com/path?q=test) → works as-is
Common mistake: Escaping characters inside code blocks. If you write `price: $19\.99` inside backticks, the backslash will be visible in the message. Don't escape inside code.

Formatting Reference

StyleSyntaxResult
Bold*bold text*bold text
Italic_italic text_italic text
Underline__underline__underline
Strikethrough~strikethrough~strikethrough
Inline code`code`code
Code block```code```(monospace block)
Link[text](url)clickable link
Spoiler||spoiler||(hidden text)

Python Escape Function

import re

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

def escape_mdv2(text: str) -> str:
    """Escape all MarkdownV2 special characters."""
    return re.sub(r'([' + re.escape(SPECIAL_CHARS) + r'])', r'\\\1', text)

# Usage:
message = f"Price: {escape_mdv2('$19.99')} \\- *bold stays bold*"
bot.send_message(chat_id, message, parse_mode="MarkdownV2")

Node.js Escape Function

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

// Usage:
const price = escapeMdV2('$19.99');
const msg = `Price: ${price} \\- *bold stays bold*`;
bot.sendMessage(chatId, msg, { parse_mode: 'MarkdownV2' });
Pro tip: Many experienced developers recommend using parse_mode: "HTML" instead of MarkdownV2. HTML only requires escaping <, >, and &, which is far simpler. MarkdownV2 is more powerful (supports spoilers, underline) but much more error-prone.

HTML vs MarkdownV2 — Which Should You Use?

For most bots, HTML is the safer choice. It uses familiar tags like <b>, <i>, <code>, and only three characters need escaping. MarkdownV2 is better if you need spoiler text (||hidden||) or underline (__text__), which HTML doesn't support in Telegram.

Don't want to escape characters manually? Use our free Formatter tool — it auto-escapes all 18 characters while preserving formatting, links, and code blocks.

Try the Telegram Formatter Tool →