Text Entities and Button Styling ================================ *Bot API 9.4 and 9.5 — February and March 2026* Two releases widened what a message can say without carrying media: custom emoji anywhere in the text, dates that each reader sees in their own locale and timezone, and buttons that can be coloured and carry an emoji. The formatting syntax itself is covered in :doc:`/topics/text-formatting`. This page is about what the newer entities mean and when to reach for them. ----- Formatted dates --------------- ``MessageEntityFormattedDate`` — :obj:`~pyrogram.enums.MessageEntityType`'s ``DATE_TIME`` — holds a Unix timestamp, and every client renders it in the reader's own timezone and language. A deadline written this way reads correctly in Tokyo and in Lisbon. In HTML it is ``tg-time``: .. code-block:: python from pyrogram.enums import ParseMode await app.send_message( chat_id="me", text='Maintenance starts then.', parse_mode=ParseMode.HTML, ) ``format`` is a set of single-letter flags: ============ ==================================================== Flag Renders ============ ==================================================== ``r`` relative — "in 3 hours", "2 days ago"; used alone ``w`` day of the week ``d`` short date ``D`` long date ``t`` short time ``T`` long time ============ ==================================================== Combine them (``"Dt"`` is a long date with a short time). ``r`` is exclusive: given alone it makes the whole thing relative, and it ignores the rest. Custom emoji ------------ ``CUSTOM_EMOJI`` entities replace a run of text with an animated emoji from a pack. The document id is what identifies it: .. code-block:: python await app.send_message( chat_id="me", text='👍 shipped', parse_mode=ParseMode.HTML, ) Always keep a real emoji as the tag's text. That is what clients without the pack — and every notification preview — will show instead. :meth:`~pyrogram.Client.get_custom_emoji_stickers` resolves ids back to the stickers behind them, which is how you find out what an incoming entity actually depicts. Styled buttons -------------- :obj:`~pyrogram.types.InlineKeyboardButton` takes ``style`` and ``icon_custom_emoji_id``: .. code-block:: python from pyrogram import enums from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton await app.send_message( chat_id="me", text="Delete this backup?", reply_markup=InlineKeyboardMarkup([[ InlineKeyboardButton("Delete", callback_data="del", style=enums.ButtonStyle.DANGER), InlineKeyboardButton("Keep", callback_data="keep", style=enums.ButtonStyle.SUCCESS), ]]), ) :obj:`~pyrogram.enums.ButtonStyle` is ``DEFAULT``, ``PRIMARY``, ``DANGER`` or ``SUCCESS``. Colour carries meaning here — use ``DANGER`` for the destructive choice rather than for emphasis. Diff entities ------------- ``DIFF_INSERT``, ``DIFF_REPLACE`` and ``DIFF_DELETE`` mark up a *change* to text rather than the text itself. They are what Telegram's AI writing tools use to show what they altered, and they arrive from :meth:`~pyrogram.Client.compose_text_with_ai` and :meth:`~pyrogram.Client.fix_text_with_ai`. Gotchas ------- - A formatted date entity needs text to cover, just like bold does. Give it something readable as a fallback — the entity replaces it, but only where the client understands it. - Custom emoji ids belong to a pack, not to your bot. If the pack is removed, the fallback text is all that is left. - Entity offsets are in UTF-16 code units. wzgram handles that for you when you use a parse mode; building entities by hand is where the surprises are.