Free tool

Telegram text formatter: bold, italic, spoiler, MarkdownV2

Type with bold, italic, underline, strikethrough, spoilers, code, links and quotes, watch the preview update, then copy the text the way Telegram wants it: MarkdownV2 with every symbol escaped, HTML, or entities with UTF-16 offsets.

Markup: **bold**, __italic__, ++underline++, ~~strikethrough~~, ||spoiler||, `code`, a ``` fence on its own line for a code block, [text](url), a line starting with > for a quote and >> for an expandable quote. A backslash keeps a symbol literal. Ctrl or Cmd with B, I and U work too.

155 of 4,096 characters · 155 UTF-16 units, the unit entity offsets are counted in

Preview

Launch day. The new collection is live: 49 29 USD, and the first 50 orders ship free. Details: the shop
Ships worldwide from Monday.
Puts the text on the clipboard as HTML and as plain text. Whether a paste keeps the formatting depends on the Telegram app you paste into; if it arrives plain, use the MarkdownV2 or HTML output below.

For bots and the Bot API

MarkdownV2
*Launch day\.* The new _collection_ is live: ~49~ 29 USD, and the first 50 orders ship free\.
||Discount code: FIRST50||
Details: [the shop](https://example.com/shop)
>Ships worldwide from Monday\.

Send with parse_mode=MarkdownV2. Every special character is already escaped.

HTML
<b>Launch day.</b> The new <i>collection</i> is live: <s>49</s> 29 USD, and the first 50 orders ship free.
<tg-spoiler>Discount code: FIRST50</tg-spoiler>
Details: <a href="https://example.com/shop">the shop</a>
<blockquote>Ships worldwide from Monday.</blockquote>

Send with parse_mode=HTML. The <, > and & in your text are already replaced.

Entities
[
  {
    "type": "bold",
    "offset": 0,
    "length": 11
  },
  {
    "type": "italic",
    "offset": 20,
    "length": 10
  },
  {
    "type": "strikethrough",
    "offset": 40,
    "length": 2
  },
  {
    "type": "spoiler",
    "offset": 86,
    "length": 22
  },
  {
    "type": "text_link",
    "offset": 118,
    "length": 8,
    "url": "https://example.com/shop"
  },
  {
    "type": "blockquote",
    "offset": 127,
    "length": 28
  }
]

Send the plain text as text and this array as entities, with no parse_mode. Offsets and lengths are UTF-16 code units.

Limits are counted on the text after markup is removed: 4,096 characters for a message, 1,024 for a caption. How the counting works

Formatting in Telegram is not typography, it is data. A bold word is an entity: a type, an offset and a length attached to plain text. Apps draw it, bots send it, and everything that goes wrong with a formatted message goes wrong at the point where text is turned into entities.

Ten styles, one markup

The field above takes a small markup and turns it into entities as you type: **bold**, __italic__, ++underline++, ~~strikethrough~~, ||spoiler||, a pair of backticks for inline code, a ``` fence on its own line for a code block (put the language after the opening fence), [text](url) for a link, and a line beginning with > for a quote or >> for an expandable one. A backslash in front of any of these symbols keeps it as text. Styles nest, so a link can sit inside bold and bold inside a quote; code contains nothing, and a quote contains neither another quote nor a code block. That is the set of nesting rules Telegram applies, not a limitation of this page.

The preview shows what the entities mean, not what any one app draws pixel for pixel. Spoilers stay hidden until you tap them. The counter under the field measures the text without the markup, because that is the text Telegram measures.

MarkdownV2: the escaping that breaks bots

parse_mode=MarkdownV2 is the format most tutorials show and the one behind most “can’t parse entities” errors. The rule is short and unforgiving: outside code, every one of the characters _ * [ ] ( ) ~ ` > # + - = | { } . ! must be preceded by a backslash, and so must the backslash itself. A single unescaped full stop at the end of a sentence is enough for the API to reject the whole message. Inside code and pre only the backtick and the backslash are escaped; inside the address part of a link only the closing parenthesis and the backslash.

There is one more trap. Two underscores are always read as underline, greedily, from left to right, so an italic that ends exactly where an underline ends cannot be written naively. The documented fix is to close the italic, insert an empty bold entity ** as a separator and only then close the underline. The MarkdownV2 output above does all of this, separator included, which is why it is worth copying rather than retyping.

HTML: three characters to escape

parse_mode=HTML is the calmer option. Telegram supports a fixed list of tags: b, i, u, s, tg-spoiler, a, code, pre (with a nested code class="language-…" to name the language) and blockquote, plain or expandable. Only three characters of your text need attention: <, > and &, replaced with &lt;, &gt; and &amp;. Everything else, including the full stops and dashes that make MarkdownV2 fragile, is sent as it is. It is the format AdminHub itself uses when it publishes posts, and the one to pick for any bot written from scratch.

Entities, if you would rather not escape at all

The third output is the entities array. Send it together with the plain text and no parse_mode, and there is nothing to escape, because nothing is parsed. The one thing to know is the unit: offsets and lengths are counted in UTF-16 code units, so an emoji or any character outside the Basic Multilingual Plane counts as two. The counter shows that number separately for exactly this reason.

How long a message can be

A message holds 4,096 characters and a media caption 1,024, both “after entities parsing”: markup, tags and escaping backslashes are not counted, only the text a reader sees. The limits are stated in characters, which Telegram’s client configuration counts as code points, so a bold emoji is one character against the limit even though its entity is two units long. The counter above follows that rule and switches between the two limits; the font generator explains the same distinction for styled Unicode letters.

What this tool does not do

Custom emoji and tg-time date entities need a bot with specific rights and are left out. The legacy Markdown mode is left out on purpose: it cannot nest, cannot express underline, strikethrough, spoilers or quotes, and Telegram’s own documentation points to MarkdownV2 instead. Headings, tables and lists are not part of ordinary message formatting either: Telegram introduced them in July 2026 as a separate rich text editor for Premium users, and for bots they are the rich messages of Bot API 10.1, which is what the AdminHub post editor produces. For the styles on this page, every Telegram client renders the result the same way.


Sources, checked September 2026: Bot API, Formatting options for the MarkdownV2 escape rules, the HTML tag list, the nesting rules and the “after entities parsing” limits; Bot API, MessageEntity for offsets and lengths in UTF-16 code units; Styled text with message entities for how clients count entity lengths; Telegram blog, 14 July 2026 for the rich text editor and its Premium-only availability.

What people usually ask

How do I make text bold in Telegram?
Write it here between double asterisks, or select it and press Bold, then copy the result in the form you need. For a bot, send the text as <b>bold</b> with parse_mode=HTML, or as *bold* with parse_mode=MarkdownV2, where every other special character has to be escaped. This page covers what the Bot API documents, not the buttons of each app.
Why does my bot get the error can't parse entities?
Because the message was sent with parse_mode=MarkdownV2 and contains an unescaped special character, most often a full stop, a dash, an exclamation mark or a parenthesis, or a style that was opened and never closed. With HTML the same error comes from an unclosed or unsupported tag, or from a bare < or & in the text. Paste the text here and copy the MarkdownV2 or HTML output: the escaping and the closing are already done.
HTML or MarkdownV2: which should I use for a bot?
HTML, unless you have a reason not to. It escapes three characters instead of eighteen, it has no underline-versus-italic ambiguity, and it is the format AdminHub sends to Telegram for every formatted post. MarkdownV2 is fine when your library builds it for you or when you copy it from a tool like this one.
How do I hide text behind a spoiler?
Wrap it in double vertical bars, ||like this||, or select it and press Spoiler. In MarkdownV2 a spoiler is written the same way, in HTML it is the tg-spoiler tag, and as an entity its type is spoiler. Readers see a blurred block until they tap it.
Can I use headings, tables or lists in a Telegram message?
Not with ordinary message formatting, and this tool does not produce them. Telegram added headings, tables and lists in July 2026 as a separate rich text editor for Premium users; for bots they belong to the rich messages of Bot API 10.1, which is how the AdminHub post editor sends them. Every style on this page works in any chat.
How long can a formatted message be?
4,096 characters for a message and 1,024 for a media caption, counted on the text after the markup is removed: tags, asterisks and escaping backslashes do not count. The counter under the field applies the same rule and can switch between the two limits.
Why is the entity length different from the character count?
Entity offsets and lengths are counted in UTF-16 code units, so an emoji or any character outside the basic plane counts as two, while the message limit is counted in characters. The counter shows both numbers: the first decides whether the message fits, the second is what goes into the entities array.