Skip to content
TGWAY
Channels & Bots STEP 90 / 99

Telegram Message Formatting: Entities and UTF-16 Lengths

Telegram formatting is a list of entities with type, offset and length counted in UTF-16 units. Why emoji shift your ranges, and how bots use parse_mode.

  • Applies toDevelopers
  • LevelL4
  • Reading time3 min
  • Verified

The short answer

  1. 1Formatting is a separate list of entities: type, offset and length.
  2. 2Offsets and lengths count UTF-16 units, so emoji take 2 or more.
  3. 3Bots can use parse_mode or pass entities; escape user input.
In this article
  1. Formatting sits beside the text, not inside it
  2. Positions are counted in UTF-16 units
  3. Where Markdown and HTML get converted
  4. FAQ
  5. At a glance

Formatting sits beside the text, not inside it

Bold, italics, links and other formatting in a Telegram message are not stored as ** or tags inside the text. The text is plain characters, and the formatting is attached separately as a list of entities. Each entity has three parts: a type, a starting position (offset) and a length (length).

text:     Hello and welcome
entities: [{type: bold, offset: 0, length: 5}]
          -> only "Hello" is bold
GroupEntity types (examples)
Text styleBold, italic, underline, strikethrough, spoiler
CodeInline code, code block (optionally with a language name)
Links and peopleText links, user mentions, mentions by name (for people without a username)
OtherCustom emoji, quotes, expandable quotes

Things the server can recognise on its own, such as URLs, hashtags, @usernames and /commands, get entities automatically. Entities can overlap (bold italic, for example), but some types, such as code blocks, cannot contain other formatting.

Positions are counted in UTF-16 units

This is the most common mistake. offset and length are not character counts; they are counted in UTF-16 code units.

CharacterUTF-16 units
A Latin letter, or a Korean or Chinese character1
Most emoji (characters outside the Basic Multilingual Plane)2
Combined emoji such as skin tones or familiesThe sum of their parts: 4, 7, 11 and more

If you compute positions in a language that counts characters (code points), such as Python's len(), every formatting range after an emoji shifts by one or two places. Use the length of the string encoded as UTF-16 (bytes divided by two), or the native length in a language that already counts in UTF-16, such as JavaScript.

def u16(s):  # length in UTF-16 units
    return len(s.encode("utf-16-le")) // 2

Where Markdown and HTML get converted

  • User-account API: the app (client) converts what the user typed in Markdown or HTML into entities, and sends the server plain text plus the entity list.
  • Bot API: set parse_mode to MarkdownV2 or HTML and Telegram converts it for you. You can also pass an entity list directly (entities); positions are still in UTF-16 units. See your first Bot API requests.

How the formatting looks to people writing messages is covered in formatting text in Telegram.

FAQ

Why is my Telegram bot's bold text in the wrong place?

Offsets are counted in UTF-16 units. Emoji count as two or more, so counting characters shifts later ranges.

How do I calculate entity offsets in Python?

Use len(text.encode('utf-16-le')) // 2 for lengths instead of len(text).

What happens if user input contains Markdown symbols?

With MarkdownV2 it can break formatting or be rejected. Escape the symbols or send entities directly.

Sources

Summary

At a glance

Telegram Message Formatting: Entities and UTF-16 Lengths — at a glance illustration
  1. 1Entities

    Plain text plus a list of formatted ranges.

  2. 2UTF-16

    Count encoded length, not characters, or ranges shift.

  3. 3Bots

    MarkdownV2 or HTML via parse_mode, or entities directly.

Share this guide

Send it to a friend who has the same question.

Telegram WhatsApp
More

Open on your phonePoint your phone camera here to open this guide.

Updated

Related articles

View all ›