Text Formatting & Escaping
Telegram supports rich text formatting via HTML, MarkdownV2, and legacy Markdown. However, unescaped characters (such as _, *, [, <, >, &) frequently cause Telegram API errors like 400 Bad Request: can't parse entities.
tueen/telegram provides two complementary utilities strictly adhering to the Telegram Bot API Formatting Options specification:
TextBuilder: A safe, fluent, nesting-capable builder for constructing styled messages.EscapeHelper: Low-level escaping functions for user-generated strings.
🎨 1. Fluent Text Builder
The Text builder lets you chain formatting methods while automatically escaping any dynamic or user-supplied variables.
Direct API Integration
You can pass Text instances directly into Telegram methods like $bot->sendMessage() or $bot->sendPhoto(caption: ...). Tueen automatically casts the text to a string and auto-sets parseMode to the builder's mode:
use Tueen\Telegram\Formatting\Text;
// No need to manually cast (string) or specify parseMode:
$bot->sendMessage(
chatId: $chatId,
text: Text::html()
->bold('Invoice #100')->line()
->plain('Total: $50.00')
);HTML Formatting (Text::html)
$message = Text::html()
->bold('Order Confirmation')->line()
->line()
->plain('User: ')->userMention($user->name, $user->id)->line()
->plain('Item: ')->bold($item->name)->line()
->plain('Price: ')->code("$" . number_format($item->price, 2))->line()
->line()
->blockquote('Thank you for shopping with us!')
->line()
->link('Track Package', 'https://track.example.com/12345');MarkdownV2 Formatting (Text::markdownV2)
You can generate MarkdownV2 without manually escaping the 18 reserved Telegram characters:
$message = Text::markdownV2()
->bold('System Alert!')->line()
->spoiler('Secret token inside')->line()
->expandableBlockquote("Log line 1: OK\nLog line 2: WARNING\nLog line 3: SUCCESS")
->line()
->pre("<?php\necho 'Hello World!';", language: 'php');🔗 2. Nested Formatting Styles
Telegram allows entities to be nested (e.g. bold italic text or links with bold labels). Text supports nesting using Closures, sub-Text instances, or strings:
// Bold italic text: <b><i>Bold and Italic</i></b>
$text = Text::html()->bold(fn(Text $t) => $t->italic('Bold and Italic'));
// Bold spoiler in MarkdownV2: *||Secret||*
$text = Text::markdownV2()->bold(fn(Text $t) => $t->spoiler('Secret'));
// Hyperlink with bold label
$link = Text::html()->link(fn(Text $t) => $t->bold('Click Here'), 'https://example.com');🛡️ 3. Low-Level Escaping with Escape
When constructing strings manually or templating with Blade/Twig, use Tueen\Telegram\Formatting\Escape:
use Tueen\Telegram\Formatting\Escape;
// 1. Escaping for HTML
$safeHtml = Escape::html("<script>alert('xss');</script> & Bob");
// Returns: <script>alert('xss');</script> & Bob
// 2. Escaping for MarkdownV2 (escapes 18 reserved Telegram characters)
$safeMarkdown = Escape::markdownV2("Price is [10.00$] *special*");
// Returns: Price is \[10\.00\$\] \*special\*
// 3. Escaping URL query components
$safeUrl = Escape::urlParam("https://example.com?query=hello world");🧭 4. Formatting API Catalog
Below is the complete reference of methods available on the Text builder and Escape helper.
🎨 Text Builder (Tueen\Telegram\Formatting\Text)
bold(string|callable|Text $content): selfitalic(string|callable|Text $content): selfunderline(string|callable|Text $content): selfstrikethrough(string|callable|Text $content): selfspoiler(string|callable|Text $content): selfblockquote(string|callable|Text $content): selfexpandableBlockquote(string|callable|Text $content): selfcode(string $code): selfpre(string $code, ?string $language = null): selflink(string|callable|Text $content, string $url): selfuserMention(string|callable|Text $content, int $userId): selfcustomEmoji(string $emoji, string $customEmojiId): selfplain(string $text): selfraw(string $string): selfline(string|callable|Text|null $content = null): selfbr(int $count = 1): selfhr(int $length = 25): self🛡️ Escape Helper (Tueen\Telegram\Formatting\Escape)
Escape::html(string $text): stringEscape::markdownV2(string $text): stringEscape::markdown(string $text): string