it-healer / laravel-telegram-bot
A library for convenient creation of Telegram bots by analogy with the creation of Web sites on Laravel.
Requires
- php: ^8.3|^8.4|^8.5
- ext-dom: *
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
- symfony/dom-crawler: ^6.0|^7.0
- symfony/polyfill-mbstring: ^1.0
Requires (Dev)
- laravel/pint: ^1.14
- nunomaduro/collision: ^7.10.0||^8.1.1
- orchestra/testbench: ^8.22.0||^9.0.0||^10.0.0||^11.0.0
- pestphp/pest: ^2.34||^3.0
- pestphp/pest-plugin-arch: ^2.7||^3.0
- pestphp/pest-plugin-laravel: ^2.3||^3.0
README
This package for Laravel 11+ allows you to easily create interactive Telegram bots, using Laravel routing, and using Blade templates to conduct a dialogue with the user.
Supports Laravel 11, 12 and 13, PHP 8.3, 8.4 and 8.5.
Installation
You can install the package via composer:
composer require it-healer/laravel-telegram-bot
php artisan telegram:install
You can publish and run the migrations with:
php artisan vendor:publish --tag="telegram-migrations"
php artisan migrate
You can publish the config file with:
php artisan vendor:publish --tag="telegram-config"
Optionally, you can publish the views using:
php artisan vendor:publish --tag="telegram-views"
Optionally, if you use Sail for local development, you need add PHP params PHP_CLI_SERVER_WORKERS="10" in file supervisord.conf:
[program:php] command=%(ENV_SUPERVISOR_PHP_COMMAND)s user=%(ENV_SUPERVISOR_PHP_USER)s environment=LARAVEL_SAIL="1",PHP_CLI_SERVER_WORKERS="10" stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0
Authentication
You can use Laravel Auth, edit file config/auth.php and edit section guards:
'guards' => [ 'web' => [...], 'telegram' => [ 'driver' => 'telegram', 'provider' => 'users', ] ],
After this you can use middleware auth:telegram (and guest:telegram) in your routes.
The telegram guard is a stateful guard that links a Telegram chat to your authenticatable model through the telegram_users table. Use it like any other guard:
// Log in by credentials (any column except password, e.g. email or login): Auth::guard('telegram')->attempt(['login' => $login, 'password' => $password]); // Log in a known user instance: Auth::guard('telegram')->login($user); // Current user / logout: Auth::guard('telegram')->user(); Auth::guard('telegram')->logout();
When auth:telegram blocks a guest, it redirects to the route named telegram.user.auth. When guest:telegram blocks an authenticated user, it redirects to the first existing route among telegram.dashboard, telegram.home, telegram.index.
Scheduling (optional)
If you want to work with automatic truncate of dialogs, run the command php artisan telegram:truncate every minute using Schedule.
To live-refresh pages, register the telegram.live middleware alias:
->withMiddleware(function (Middleware $middleware) { $middleware->alias([ 'telegram.live' => \ItHealer\Telegram\Middleware\LiveMiddleware::class, ]); })
Then use it in routes (the argument is the refresh frequency in seconds):
Route::telegram('/', [\App\Telegram\Controllers\MyController::class, 'index']) ->middleware(['telegram.live:30']);
In file routes/console.php add:
Schedule::command('telegram:live')->runInBackground()->everyMinute(); Schedule::command('telegram:truncate')->everyMinute();
Bot management commands
php artisan telegram:new-bot # Register a new bot (asks for the token) php artisan telegram:init # Apply bot settings (name, description, commands) from config php artisan telegram:set-webhook # Set the webhook for a bot php artisan telegram:unset-webhook # Remove the webhook php artisan telegram:pooling [BOT_ID] # Manual long-polling (useful on localhost) php artisan telegram:truncate # Clean up old dialog messages php artisan telegram:live # Process live-refreshing pages
Routing
Bot screens are defined in routes/telegram.php with the Route::telegram() macro. Routes are automatically named with a telegram. prefix.
use Illuminate\Support\Facades\Route; use ItHealer\Telegram\TelegramRequest; Route::telegram('/', function (TelegramRequest $request) { return '<message><line>You wrote: '.$request->text().'</line></message>'; })->name('home'); Route::telegram('/profile', [\App\Telegram\Controllers\ProfileController::class, 'index']) ->name('profile') ->middleware('auth:telegram');
A handler may return a string of bot HTML, a view(...), or a redirect(...) — redirects are followed internally and re-dispatched as another bot screen.
The TelegramRequest exposes the incoming update: text(), post($key), query(), chat(), user(), etc.
Messages and formatting
Bot output is written as HTML. The top-level tags are <message>, media tags (see below), and <screen>.
<message> <line>First line</line> <line><b>Bold</b>, <i>italic</i>, <u>underline</u>, <s>strike</s>, <code>code</code>, <a href="https://example.com">link</a></line> <lines>Multi-line / pre-formatted block kept as-is</lines> </message>
<line>— a single line (newlines inside are stripped).<lines>— a block whose inner HTML is preserved as-is.- Inside lines you can use Telegram HTML formatting tags (
<b>,<i>,<u>,<s>,<code>,<pre>,<a>,<tg-spoiler>, …). Messages are sent withparse_mode=html.
The <screen> tag
By default, messages are appended to the dialog ("classic" mode). When you wrap your output in <screen>, the bot clears the current dialog and redraws everything inside from scratch — useful for single-screen, app-like navigation where each step replaces the previous one.
<screen> <message> <line>🏠 <b>Main menu</b></line> <inline-keyboard> <row> <column data-redirect="/profile">Profile</column> </row> </inline-keyboard> </message> </screen>
You can place several messages/media inside one <screen>. Mixing <screen> and top-level messages produces a "mixed" render (the screen part is redrawn, the rest is appended).
Media messages
Send media by using a media tag with a src attribute (a local file path, a URL, or a Telegram file_id). Captions are written with <line>/<lines> inside the tag.
<photo src="/path/or/url/or/file_id"> <line>Caption with <b>formatting</b></line> </photo> <document src="..."><line>A file</line></document> <video src="..." show_caption_above_media="true"><line>A video</line></video> <voice src="..."></voice> <video-note src="..."></video-note>
src— local path, URL orfile_id. Uploaded files are cached by content hash, so the same file is only uploaded once and reused byfile_id.show_caption_above_media="true"— show the caption above the media.reply-message-id="123"— reply to a specific message (works on<message>and media tags).
Inline keyboards
<inline-keyboard> <row> <column query-param="value">Change a query param</column> <column data-field="value">Send POST data</column> </row> <row> <column data-field="long value" encode="true">Encoded POST data</column> <column data-redirect="/profile">Redirect to a screen</column> </row> <row> <column url="https://example.com">Open a URL</column> <column web_app="https://example.com/app">Open a Web App</column> </row> </inline-keyboard>
Supported <column> attributes:
| Attribute | Description |
|---|---|
query-* |
Set/override a query string parameter on the current screen. |
data-* |
Send POST data to the handler ($request->post('field')). |
encode="true" |
Encode long POST data via cache (default true when data-redirect is set). |
data-redirect="/uri" |
Navigate (redirect) to another screen. |
data-current="field" |
Edit-form: switch the current field (see Edit Form). |
data-submit="true" |
Edit-form: submit the form. |
url="..." |
Open an external URL. |
web_app="..." |
Open a Telegram Web App. |
style="..." |
Button color (Bot API 9.4+): primary (blue), success (green) or danger (red). |
icon="<custom_emoji_id>" |
Show a custom emoji on the button (icon_custom_emoji_id, Bot API 9.4+). |
Colored buttons (button styles)
Since Bot API 9.4 you can color inline (and reply) keyboard buttons with the style attribute:
<inline-keyboard> <row> <column data-redirect="/login" style="primary">🔑 Log in</column> <column data-redirect="/register" style="success">📝 Register</column> </row> <row> <column data-redirect="/delete" style="danger">🗑 Delete</column> </row> </inline-keyboard>
Allowed values: primary, success, danger. Omit style for the default look. Old Telegram clients simply ignore the field. Invalid values throw an InvalidArgumentException.
You can do the same from PHP on the DTO:
use ItHealer\Telegram\DTO\InlineKeyboard\Button; Button::make() ->setText('Delete') ->setCallbackData(['action' => 'delete']) ->setStyle('danger') ->setIconCustomEmojiId('5368324170671202286');
Reply keyboards
<message> <line>Choose:</line> <reply-keyboard resize="true" persistent="true"> <row> <column request_contact="true">Share contact</column> <column request_location="true">Share location</column> </row> <row> <column web_app="https://example.com/app">Web App</column> <column style="primary">Styled button</column> </row> </reply-keyboard> </message>
Supported <reply-keyboard> attributes: resize, persistent. Supported <column> attributes: request_contact, request_location, web_app, style, icon.
Edit Form
class MyForm extends \ItHealer\Telegram\EditForm\BaseForm { public function rules(): array { return [ 'name' => ['required', 'string', 'min:5', 'max:255'], 'phone' => ['required', 'string', 'min:10', 'max:15'], ]; } public function titles(): array { return [ 'name' => 'Your name', 'phone' => 'Your phone number' ]; } }
class MyController { public function edit(MyForm $form): mixed { $form->setDefault([ 'name' => 'Default name', 'phone' => '1234567890', ]); if( $form->validate() ) { // $form->get(); } return view('...', compact('form')); } public function create(MyForm $form): mixed { if( $form->isCreate()->validate() ) { // $form->get(); } return view('...', compact('form')); } }
<message> <x-telegram-edit-form :form="$form"> <x-slot:name> <line>Please, enter your First Name:</line> </x-slot:name> </x-telegram-edit-form> </message>
isCreate() walks through the fields one by one (asking each in turn) and returns true on submit; without it the form lets the user edit any field. Use optional() to allow empty values (the user can type /empty).
Sending and editing messages from code
Use ChatAPI (e.g. via $telegramChat->api()):
$api = $chat->api(); $api->send($message); // send a Message DTO (text/photo/video/document/voice/...) $api->edit($oldMessage, $newMessage); // edit an existing message $api->delete($message); // delete message(s) $api->deleteMessages($id1, $id2); // delete by message id(s) $api->sendChatAction(ChatAction::Typing); $api->getUserProfilePhotos();
The bot-level API (via $bot->api()) provides getMe() and getFileLink($file).
Configuration
Key options in config/telegram.php:
reactions— map incoming commands/emoji (e.g./start,🏠,/back,/refresh) to navigation actions.screen.ttl/screen.truncate— lifetime of dialog messages and auto-truncate window.page.timeout/page.wait/page.delay/page.max_redirects— request handling tuning.cache.ttl/cache.encode_ttl— TTL for cachedfile_ids and encoded callback payloads.models.bot/models.chat/models.user— override the package's Eloquent models.webhook.background— handle webhook updates in the background.init— default bot name/description/commands applied bytelegram:init.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Custom development & contacts / Заказная разработка и контакты
EN — Need a new project built from scratch, or this package integrated into your existing application? Contact the developer directly — custom development, module integration and ongoing support are available.
RU — Нужен новый проект «под ключ» или интеграция этого пакета в существующее приложение? Свяжитесь с разработчиком напрямую — доступны заказная разработка, интеграция модулей и поддержка.
- 🌐 Website / Сайт: it-healer.com
- ✈️ Telegram: @biodynamist · +90 551 629 47 16
- 📱 WhatsApp: +90 551 629 47 16
- 📧 Email: info@it-healer.com
- 🐛 Issues / Баг-репорты: GitHub Issues
Credits
License
The MIT License (MIT). Please see License File for more information.