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

Laragram Laravel Package

milly/laragram

Laragram is a Laravel package for building Telegram bots with a clean, expressive API. It wraps Telegram Bot API calls and follows Laravel conventions for configuration, routing, webhooks, commands, and responses, making bot development simpler and more maintainable.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish. composer require milly/laragram, then publish the config (php artisan vendor:publish --tag=laragram-config — check php artisan vendor:publish output, tag names vary). This gives you config/laragram.php, where the bot token and default webhook route are configured.
  2. Set the token. Add TELEGRAM_BOT_TOKEN=... (or whatever key the config file references) to .env. If the config reads env('LARAGRAM_TOKEN'), use that name instead — grep the config file rather than guessing.
  3. Find the entry points. Open the service provider (vendor/milly/laragram/src/...ServiceProvider.php) to see what is bound into the container and which facade alias is registered. That facade is your day-to-day API surface for sending messages.
  4. Register the webhook. Point Telegram at your app: curl "https://api.telegram.org/bot$TOKEN/setWebhook?url=https://your.app/laragram/webhook" — or use the package's artisan command if one exists (php artisan list | grep -i laragram).
  5. First use case: echo bot. Send a message to your bot, log the raw update inside the webhook handler, then reply with the facade:
public function __invoke(Request $request)
{
    Log::info('tg update', $request->all());
    $chatId = $request->input('message.chat.id');
    $text   = $request->input('message.text');

    Laragram::sendMessage($chatId, "You said: {$text}");

    return response()->noContent();
}

Once the echo round-trips, you have verified token, webhook URL, route, and outbound call in one pass.

Implementation Patterns

Thin controller, fat handler. Keep the webhook controller to route resolution and dispatch. Move command logic into a HandleTelegramUpdate job or a dedicated handler class so you can queue it and test it without HTTP. The webhook endpoint must return 200 fast or Telegram retries the same update.

Command routing via a match on the first token.

[$command, $args] = array_pad(explode(' ', $text, 2), 2, null);

match ($command) {
    '/start'  => $this->start($chatId),
    '/help'   => $this->help($chatId),
    default   => $this->fallback($chatId, $text),
};

Strip the @BotName suffix Telegram appends in groups (/start@MyBot) before matching.

Queue outbound calls. Wrap every send in a job with retry(3) and a small backoff. Telegram latency spikes will otherwise stall your webhook response, cause retries, and produce duplicate replies. A simple SendTelegramMessage job that calls the facade is enough.

Idempotency. Persist update_id in a unique-indexed table (or cache with TTL) before processing. Telegram redelivers on non-2xx or timeout, and users double-tap. This is the single highest-value addition on top of a thin package.

Long polling for local dev. Webhooks need a public URL. Either run ngrok http 8000 and re-setWebhook each session, or add a local artisan command that loops getUpdates with an offset — useful when the package exposes the raw client.

Conversation state. For multi-step flows (wizards, forms), key state by chat_id in cache or a bot_states table. Don't rely on the package for this; almost no small bot package provides it, and rolling your own keeps it inspectable.

Integration tip. If the facade hides the endpoint you need (polls, inline keyboards, media groups, payments), don't fight it — use Laravel's Http facade directly against https://api.telegram.org/bot{token}/{method}. Mixing the two is fine; just centralize the base URL and token in one helper.

Gotchas and Tips

  • Config caching. php artisan config:cache freezes the token and webhook path. After changing either in .env, re-run config:clear. A stale token in a cached config looks exactly like a revoked token (401 from Telegram).
  • X-Telegram-Bot-Api-Secret-Token. Pass secret_token to setWebhook and verify the header in middleware. Without it, your public webhook is an open endpoint anyone can POST fake updates to. Most thin packages do not do this for you.
  • Markdown parsing. parse_mode=Markdown breaks on unescaped _ * [ ] ( ). MarkdownV2 is stricter still. If messages silently fail, drop parse_mode first — that isolates formatting from delivery.
  • 4096-character limit. Long output returns 400. Chunk on paragraph boundaries, or send as a document.
  • File uploads need multipart. Sending photo/document as JSON with a URL works; sending a local file requires multipart form data. The facade may not abstract this — check before assuming.
  • Webhook switching. When moving from polling to webhook, call setWebhook with drop_pending_updates=true, or the bot replays a backlog of stale messages on first connect.
  • 409 Conflict. Two processes calling getUpdates with long polling produce 409 Conflict: terminated by other getUpdates request. Usually means a leftover polling worker or a second webhook consumer.
  • Rate limits. Roughly 30 messages/sec globally and ~1/sec per chat for bulk sends. A 429 body contains retry_after — respect it in your retry loop rather than hammering.
  • Debugging blind. getWebhookInfo tells you last_error_message, pending_update_count, and whether the URL is set at all. Check it before touching code.
  • Maturity caveat. With ~41 stars, no dependents, and a 2025 release, expect thin docs and partial Bot API coverage. Pin the version in composer.json, read the source in vendor/ when behavior surprises you, and keep the raw Http fallback in reach.
  • Extension points to look for. A container-bound client (swap it for a fake in tests), an event fired per update (hook logging/analytics there), and a configurable webhook path (mount it under an existing route group with your auth/CSRF exemptions). If none exist, a small wrapper service of your own around the package is usually cheaper than forking.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle