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

wekser/laragram

Laragram is a Laravel package that wraps the Telegram Bot API in a clean, Laravel-native way. Build Telegram bots with simple configuration, fluent message sending, webhook handling, and easy integration into your existing Laravel app.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Laravel package for sending Telegram messages from your app — notifications, alerts, and inline keyboards with buttons. The v2.3.2 release is a pure fix: upgrade with composer update, nothing to run, no breaking changes. Start there if you're already on 2.x; if you're picking the package up fresh, install it and publish its config as usual, then look at BotClient (the thing that actually talks to Telegram) and Support\ReplyMarkup (the value object you'll use to build inline keyboards).

First use case to get working end-to-end: send a plain message to a chat, then add one inline keyboard row with a URL button. That exercises the client, the reply markup, and the error path you'll hit most often. Once that works, the interesting bit in 2.3.2 is user-profile buttons — a tg://user?id=… URL button that deep-links to a Telegram user. Build one, send it, and confirm your message still arrives if the target user has restricted profile linking.

First place to look when something misbehaves: the warning log line. v2.3.2 logs a warning naming the buttons it dropped, which is the fastest way to discover that a message you thought had a profile button actually shipped without one.

Implementation Patterns

Treat BotClient as the single send path. Because v2.3.2 puts the retry logic inside BotClient, any code that bypasses it — raw HTTP calls, a hand-rolled Guzzle client, a queued job that reimplements the request — loses the fix. Route everything through the client, including notification channels.

Compose keyboards with ReplyMarkup rather than hand-built arrays. The new isUserLinkButton() and withoutUserLinks() helpers exist so you can reason about keyboards before sending. A useful pattern is a small builder in your app that assembles rows, then filters them:

  • Build the full keyboard including profile buttons.
  • Optionally call withoutUserLinks() yourself when you know the audience can't be deep-linked (e.g. a support queue where contacts are external).
  • Send via BotClient and let the automatic strip-and-resend cover the cases you can't predict.

Assume profile buttons are best-effort. The release notes are explicit: privacy can't be checked ahead of the call, and Telegram doesn't say which button was refused, so every tg://user?id=… button in that message is dropped. The design pattern that follows is to always carry the identity in the message body as well — a @username, a display name, or a text mention — so the message is still actionable when the button vanishes. Think of the button as decoration, the text as the contract.

Don't rely on button-presence in tests. If your test suite asserts on the exact outgoing payload of a message containing profile buttons, the strip-and-resend behaviour means your assertion depends on Telegram's response, which you're faking. Prefer asserting on the message text and on the presence of non-tg:// buttons, and test the strip path separately by faking a 400 BUTTON_USER_PRIVACY_RESTRICTED response.

Keep your own tg:// links out of the strip path. Only tg://user?id=… buttons are affected. Other tg:// links — and with no matching button present, the original exception is thrown unchanged — behave exactly as before. If you have custom error handling around sends, make sure it doesn't swallow the new retry: the client only suppresses the specific privacy refusal, everything else still bubbles.

Watch for the double-delivery myth. The release notes are worth internalising because they preempt a common panic: nothing is sent twice. Telegram rejects the whole request, so the resend is the first successful delivery. If you add queue retries or try/catch retry wrappers on top, you're not double-guarding — you're risking genuine duplicates, since a retry after a successful resend would be a real second message.

Gotchas and Tips

The dropped button is not the one you think. With multiple profile buttons on a message, a single restricted user causes all of them to be stripped, including ones that would have worked. If you're building something like a group notification listing several contacts, expect a message where some contacts lose their button even though they're linkable. Design the text so this degradation is invisible.

You cannot pre-check privacy. Don't write a guard that calls getChat or similar and decides whether to include the button — it won't tell you. The only correct strategy is the package's: send optimistically, degrade gracefully, log.

Read the warning logs in staging. The warning entry names the dropped buttons, which is your only observability into this path. If your logging config drops warning from the bot channel — common when people set a high minimum level for noisy channels — you'll be blind to it. Check that the bot's log channel emits warnings.

Config quirks around error handling. If you've configured the package to throw on API errors, the privacy refusal is the one case where the client intercepts before your handler sees it. Don't be surprised that your generic 400 handler isn't invoked; the message just arrives. Conversely, if you have a global handler that treats any 400 as a hard failure and pages someone, that alert will no longer fire for this cause.

Extension point: the ReplyMarkup helpers. isUserLinkButton() and withoutUserLinks() are public and usable outside the send path — handy for sanitising a keyboard before persisting it, or for rendering a preview in your admin UI that matches what Telegram will actually show. Reach for them instead of regexing the button array yourself; the tg://user?id=… shape has enough variation that a hand-rolled matcher will drift.

Upgrade checklist. composer update, then grep your codebase for BUTTON_USER_PRIVACY_RESTRICTED and for any custom tg:// handling you wrote as a workaround. If you built your own strip-and-retry around sends, remove it — leaving it in place means the client strips first, then your code sees a payload with no profile buttons and behaves unexpectedly, or worse, you retry a message that already succeeded.

Debugging a message that "loses" buttons: check the warning log first, then confirm the button really was a tg://user?id=… URL button rather than a callback button (which is unaffected). If neither explains it, verify the message went through BotClient at all — a notification channel that serialises to a queue and rebuilds the request somewhere else is the usual culprit.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package