Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Telegram Bot Laravel Package

nicoosh/telegram-bot

nicoosh/telegram-bot is a Laravel package for building Telegram bots with a fluent API, webhook and update handling, commands, keyboards, and Laravel-friendly configuration. It simplifies sending messages and creating interactive bot workflows without boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and get a webhook receiving updates in under fifteen minutes.

composer require nicoosh/telegram-bot
  1. Create the bot with @BotFather, grab the token, and note the bot username (without @).
  2. First place to look is vendor/nicoosh/telegram-bot/example-bot.php and config.php. They show the exact bootstrap the library expects — everything else in the docs is a variation on those two files.
  3. Skip the bundled manager.php/set.php scripts initially. In Laravel you rarely use them; wire the library up yourself so you control the route, logging, and container binding.

Minimal webhook controller:

// routes/api.php  (no CSRF, no auth, POST)
Route::post('/telegram/webhook', [TelegramController::class, 'handle']);

// app/Http/Controllers/TelegramController.php
public function handle(Request $request)
{
    $telegram = new Longman\TelegramBot\Telegram(
        config('services.telegram.token'),
        config('services.telegram.username')
    );

    // Critical: php://input is single-read; hand Laravel's raw body over explicitly.
    Longman\TelegramBot\Request::setCustomInput($request->getContent());

    return response()->json($telegram->handle()->getRawData());
}

Register the webhook once (Artisan command, tinker, or deploy hook):

Longman\TelegramBot\Request::setWebhook(['url' => 'https://your.app/api/telegram/webhook']);

First use case to build: a /start command. Create app/Telegram/Commands/StartCommand.php, extend Longman\TelegramBot\Commands\UserCommand, implement execute() returning $this->replyToChat('Hello'), and point the config's commands.paths at app/Telegram/Commands. You now have the whole pattern — everything later is more command classes and richer replies.

Implementation Patterns

Commands are the unit of work. One class per command, discovered by filename (EchoCommand.php → /echo). Use UserCommand for normal commands, SystemCommand for reserved hooks (start, help, generic, genericmessage, inlinequery, callbackquery), and AdminCommand for operator-only tools. execute() returns a ServerResponse; return it, don't echo.

Replying. Prefer the helpers over raw Request:: calls inside commands:

return $this->replyToChat($text, ['parse_mode' => 'HTML']);

Keyboards. Build ReplyKeyboardMarkup / InlineKeyboard entities, then send as reply_markup. Handle button presses in CallbackqueryCommand (a SystemCommand) and route on $this->getCallbackQuery()->getData().

Multi-step flows. The Conversation trait gives you per-chat/user state without writing your own tables:

$this->conversation = new Conversation($user_id, $chat_id, $this->getName());
// ... ask, store, advance notes, $this->conversation->update()

This requires the library's MySQL schema (structure.sql); it is the only reason you need a database at all.

Sending outside a command. For notifications, alerts, or scheduled jobs, instantiate Telegram once (the constructor registers it statically) and then call Request:: directly — no Update needed:

new Longman\TelegramBot\Telegram($token, $username);
Longman\TelegramBot\Request::sendMessage(['chat_id' => $id, 'text' => $msg]);

Bind that instance as a container singleton in a service provider so queue workers reuse one configured Telegram rather than re-scanning command folders per job.

Custom command folder. Keep bot commands in app/Telegram/Commands (config commands.paths) so they live alongside your app and are autoloaded by Composer; leave the vendor Commands folders for system commands.

Logging. TelegramLog::initError(), initDebug(), initUpdate() write to Monolog channels. Point them at storage/logs/telegram-*.log; log the raw update too when debugging — it is the only way to reproduce a bug report.

Testing. You can exercise command logic without Telegram: build an Update entity from an array fixture (new Update($array)), set up Request::setCustomInput(json_encode($fixture)), and assert on the returned ServerResponse (or on Request::getLastResponse() with a fake transport). Test the command's decision logic, not the network call.

Gotchas and Tips

  • Webhook and getUpdates are mutually exclusive. Local polling fails with 409 Conflict until you deleteWebhook. Use unsetWebhook/setWebhook in a deploy script and never run both modes against one token.
  • Read php://input exactly once. In Laravel, Request::setCustomInput($request->getContent()) must happen before handle(); forgetting it produces an empty update and silent no-ops.
  • Exclude the webhook route from CSRF, auth, and any global middleware that might reject POSTs — Telegram sends no cookies, headers, or tokens unless you set a secret path/secret token.
  • HTTPS with a public CA only. Self-signed certs and plain HTTP are rejected by setWebhook. ngrok works for local dev; a private tunnel with a self-signed cert does not.
  • Return 200 fast. Telegram retries any non-2xx or timed-out update, so a slow command yields duplicate execution. Offload heavy work (downloads, LLM calls, broadcasts) to a queue and reply immediately. Guard against re-delivery with the update_id.
  • Always check ServerResponse::isOk(). The library does not throw on API errors — getErrorCode()/getDescription() are where the truth is. Handle 429 with retry_after rather than retrying blindly, and watch the per-chat ~30 msg/s limit for broadcast jobs.
  • Entities return null. $update->getMessage() is null for callback_query and inline queries; getFrom() can be null on channel posts. Guard every accessor chain.
  • Hard limits bite: message text 4096 chars, captions 1024, inline callback_data 64 bytes, and file uploads need absolute paths wrapped in Request::encodeFile(). Chunk long text yourself.
  • Command discovery has a cost. The constructor scans command folders per request unless cached — keep custom paths tight and avoid instantiating Telegram more than once per request/job.
  • Reserved names. start, help, generic, genericmessage, inlinequery, callbackquery belong to SystemCommand; a UserCommand with that name is shadowed. Use GenericmessageCommand as a deliberate catch-all for unknown input.
  • Admin commands need an admin ID list in config. /debug dumps internals — never leave it enabled in production.
  • No logger means no errors. Failures are swallowed when TelegramLog is uninitialised; set the log paths before deploying or you will debug blind.
  • Conversations require the DB and housekeeping. Run structure.sql, and prune the conversation table — stale state silently hijacks a user's next command.
  • Keep secrets in config/services.php + .env, and give each bot instance its own webhook route if you run multiple bots — a shared Telegram singleton leaks state across them.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core