Note
Telegram premium emoji in aiogram: full example
aiogram 3 can send a custom emoji by passing a MessageEntity and leaving parse_mode unset. If you set both, you are mixing two ways of describing formatting. Pick one.
This example sends the Nigeria flag from the directory. The ID is 5411568100430587798. The message text is Hi plus the flag. The entity starts after Hi , so the offset is 3.
A country flag is two regional indicator symbols. In UTF-16 that is 4 code units, not 2. Python’s len("🇳🇬") returns 2, because it counts code points. The Bot API wants UTF-16 code units. Use the helper below.
The helper
def utf16_len(text: str) -> int:
return len(text.encode("utf-16-le")) // 2
utf-16-le encodes each code unit as two bytes, so dividing by 2 gives the count Telegram asks for. For 🇳🇬 the result is 4. For ✔️ (U+2714 plus the variation selector U+FE0F) the result is 2. For the ASCII prefix Hi the result is 3, which matches len() only because those characters are in the Basic Multilingual Plane.
The handler
import os
from aiogram import Bot, Dispatcher, Router
from aiogram.enums import MessageEntityType
from aiogram.filters import Command
from aiogram.types import Message, MessageEntity
router = Router()
FLAG = "🇳🇬"
FLAG_ID = "5411568100430587798"
def utf16_len(text: str) -> int:
return len(text.encode("utf-16-le")) // 2
@router.message(Command("ng"))
async def send_nigeria(message: Message) -> None:
prefix = "Hi "
text = prefix + FLAG
await message.answer(
text,
entities=[
MessageEntity(
type=MessageEntityType.CUSTOM_EMOJI,
offset=utf16_len(prefix),
length=utf16_len(FLAG),
custom_emoji_id=FLAG_ID,
)
],
)
async def main() -> None:
bot = Bot(os.environ["BOT_TOKEN"])
dp = Dispatcher()
dp.include_router(router)
await dp.start_polling(bot)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
FLAG_ID is a string on purpose. Do not drop the quotes.
message.answer here does not pass parse_mode. The entity list is the formatting. The same call works with bot.send_message(chat_id, text, entities=[...]) if you are not replying inside a handler.
HTML instead of entities
If you would rather write a tag, set parse_mode="HTML" and do not pass entities:
await message.answer(
'Hi <tg-emoji emoji-id="5411568100430587798">🇳🇬</tg-emoji>',
parse_mode="HTML",
)
The character inside the tag is the fallback. The Bot API says to use the emoji from the sticker’s emoji field, and to expect that fallback in places that cannot show a custom emoji, including when a non-premium user forwards the message.
The limit is not optional
The docs say custom emoji entities can only be used by bots that purchased additional usernames on Fragment, or in messages the bot sends directly to private chats, groups, and supergroups if the owner of the bot has a Telegram Premium subscription. Owning the flag pack is not the same rule. A fuller quote and the other libraries are on the guide. The Nigeria page, with every snippet already counted, is here.
If the flag shows as two letters or as the plain character, read why a custom emoji does not show.