Multi-bot Telegram framework for Laravel: declarative routing (commands, callback patterns, text triggers, arbitrary update types), a dialog engine with step-by-step conversations, webhook and long-polling delivery, out of the box.
composer require appto/telegram-bot
php artisan vendor:publish --tag=telegram-bot-config
php artisan migratefinal class ShopBot extends Bot
{
protected function register(): void
{
$this->onCommand('start', RegistrationDialog::class);
$this->onCommand('help', HelpCommand::class);
$this->onCallback('order:confirm {id}', OrderConfirmHandler::class);
$this->onText('hello', HelloHandler::class);
$this->onText('send me photo',
fn (UpdateContext $context) => $context->replyPhoto(
FileInput::fromFile(storage_path('your_file.jpg')),
caption: 'It\'s me'
)
);
}
}php artisan telegram:poll testDialogs (RegistrationDialog above) implement the same UpdateHandler contract as any other
handler — no separate registration API, they're wired through onCommand()/onText()/
onCallback() just like the rest.
Any command, callback handler, or dialog can opt in to access control by implementing
RequiresPermission:
final class AdminPanel implements CommandHandler, RequiresPermission
{
public function authorize(UpdateContext $context): bool
{
return $context->userId() === 123456;
}
}The framework calls authorize() before handle() — no wiring required. When it returns
false, the request is rejected silently by default. To customize the rejection message,
implement HasUnauthorizedMessage alongside it:
public function unauthorizedMessage(UpdateContext $context): ?string
{
return 'This command is for admins only.';
}For callback_query updates the message is delivered via answerCallbackQuery (as an alert
by default); for everything else, as a regular reply. A global fallback message can be set in
config('telegram-bot.unauthorized.message').
An opt-in HelpCommand lists every registered command that implements HasDescription:
final class StartCommand implements CommandHandler, HasDescription
{
public static function description(): string
{
return __('telegram-bot::help.start_command');
}
}$this->onCommand('help', HelpCommand::class);Only commands the requesting user is authorized for (per RequiresPermission, if
implemented) show up in their /help output. Package strings (title, empty-list message) are
translatable — see Translations below.
An alternative to webhooks for local development or environments without a public HTTPS URL:
the command repeatedly calls getUpdates and dispatches every update through the same
pipeline a webhook request would use (Bot::dispatch()).
php artisan telegram:poll shopRun without an argument to pick a bot interactively from the list of registered bots:
php artisan telegram:pollTelegram allows only one delivery method per bot — before polling starts, the command checks
whether a webhook is set and, if so, prompts you to remove it. There's no need to run
telegram:delete-webhook manually first.
| Option | Description |
|---|---|
--timeout=30 |
Long-poll timeout in seconds passed to getUpdates |
-o, --show-outgoing |
Also print the bot's outgoing API calls to the console, not just incoming updates |
--only=* |
Only show/dispatch these update types, e.g. --only=message --only=callback_query |
--user=* |
Only show/dispatch updates from these Telegram user IDs |
--dry-run |
Receive and display updates without dispatching them to the bot — inspect traffic without triggering handlers |
-l, --log-traffic |
Log raw incoming/outgoing payloads to storage/logs/telegram-traffic.log |
# watch only callback_query updates from a specific user, without running any handlers
php artisan telegram:poll shop --only=callback_query --user=123456789 --dry-runErrors during polling don't stop the command — a failed getUpdates call backs off
exponentially (5s, 10s, 20s... capped at 60s) and retries; an exception thrown by a specific
update's handler is reported and logged without interrupting the loop for subsequent updates.
Long polling holds an open foreground process — it's meant for local development, not for
running behind a process manager in production. Use a webhook (telegram:set-webhook) for
anything deployed.
php artisan telegram:routes # all bots
php artisan telegram:routes shop # a single bot
php artisan telegram:routes shop --type=commandsLists every registered command, callback pattern, and text trigger per bot, and flags which ones require authorization.
php artisan vendor:publish --tag=telegram-bot-config # config only
php artisan vendor:publish --tag=telegram-bot-migrations # migrations only
php artisan vendor:publish --tag=telegram-bot-lang # translations only
php artisan vendor:publish --provider="Appto\TelegramBot\TelegramBotServiceProvider" # everythingPackage strings are published under telegram-bot:: and can be overridden per locale by
publishing them into lang/vendor/telegram-bot/{locale}:
php artisan vendor:publish --tag=telegram-bot-langMIT