Support Telegram Bot API: v9.3 - December 31, 2025 | Telegram Group
A lightweight and idiomatic Go-framework for the Telegram Bot API. Inspired by Python libraries (Aiogram, Telebot), it provides Go developers with a convenient environment for developing Telegram bots.
- βοΈ Flexible Middleware: 3 levels of nesting (Global, Group, Local) for full control over the data flow.
- π Powerful Filters: Built-in (
IsCommand,Regexp,HasPhoto...) and easy creation of custom filters. - π Handler Grouping: Logical separation of code and application of common rules to sets of commands.
- π Concurrency: Built-in worker system for parallel and scalable update processing.
- π Webhook & Polling: Support for Long Polling and 3 Webhook modes (including integration into any external frameworks).
- π‘οΈ Stability: Automatic panic recovery (
PanicRecovery) and typed error handling. - π Strict Typing: No
anyorinterface{}, only strict parameter typing. - π File Handling: Convenient media sending via
io.Reader, file path, URL, or FileID. - π§ FSM (Finite State Machine): Convenient user dialog state management (β³ in development).
- β¨οΈ Keyboard Factory: Keyboard constructor and typed Callback Data (β³ in development).
- πͺΆ Lightweight: No external dependencies β only the standard Go library.
- π Documentation: Code follows GoDoc standards and contains links to the official Telegram Bot API documentation.
- Installation
- Quick Start
- Bot Configuration
- Framework Structure
- Handlers
- Filters
- Handler Groups
- Middleware
- Update Lifecycle
- Running the Bot
- File Handling
- Error Handling
- Logging and Debugging
- User State Management
- Handling Special Update Types
- Working with Keyboards
- Typical Usage Scenarios
- Testing
Ensure you have Go version 1.25 or higher installed.
go get -U github.com/stalkerxxl/telegnomExample of a simple bot that replies "Hello!" to the /start command.
package main
import (
"context"
"os"
"os/signal"
"syscall"
"github.com/stalkerxxl/telegnom/bot"
f "github.com/stalkerxxl/telegnom/filters"
)
func main() {
// 1. Setup context for graceful shutdown
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
// 2. Initialize the bot
b, _ := bot.NewBot(ctx, os.Getenv("BOT_TOKEN"))
// 3. Register a handler
b.Handler(func(tg *bot.Context) {
tg.Bot.SendMessage(&bot.SendMessageParams{
ChatID: tg.ChatID(),
Text: "Hello from Telegnom!",
})
}).OnMessage(f.Command("start"))
// 4. Start (blocking call)
b.StartPolling()
}When creating a bot using bot.NewBot(), you can pass various options to configure its behavior. All options are optional.
opts := []bot.Option{
bot.WithPanicRecovery(),
bot.WithNumWorkers(10),
bot.WithAllowedUpdates(types.AllUpdateTypes()...),
}
b, err := bot.NewBot(ctx, token, opts...)WithApiUrl(url string): Use a custom API URL (e.g., for a local Bot API server).WithNumWorkers(n int): Number of parallel workers for processing updates (default: 1).WithClientTimeout(d time.Duration): HTTP client timeout.WithHTTPClient(client *http.Client): Use a custom HTTP client (e.g., for proxy).WithPanicRecovery(): Enables middleware for recovering from panics in handlers.WithTestEnv(): Switches the bot to the Telegram test environment.
WithAllowedUpdates(ut ...types.UpdateType): List of update types you want to receive.WithPollTimeout(d time.Duration): Long polling timeout (default: 1 minute).WithPollLimit(limit int): Maximum number of updates retrieved per request (1-100).WithPollOffset(offset int64): Offset for receiving updates.WithUpdatesChanCap(cap int): Capacity of the internal update channel.
WithWebhookURL(url string): Sets the webhook path based on the provided URL.WithWebhookSecretToken(token string): Secret token for verifying requests from Telegram.
WithDebugHandler(h HandlerFunc): Custom handler for logging every incoming update.WithErrorHandler(h bot.ErrorHandler): Handler for system and network level errors.
bot.Context is passed to all handlers, filters, and middleware. It combines context.Context (Go), the bot object, and the current update.
tg.Bot: Access to all Telegram API methods (SendMessage,GetMe, etc.).tg.Update: Data of the current event.tg.ChatID() / tg.SenderID(): Convenient helpers for getting Chat/User ID.tg.Set(key, val) / tg.Get(key): Passing data between middleware and handlers.
The main object for configuring and managing the bot's lifecycle.
- Registration:
b.Handler(),b.Group(),b.Use(). - Startup:
b.StartPolling(),b.StartWebhook(). - API: All Telegram Bot API methods in PascalCase.
A complete typed Telegram update structure. Contains helper methods for quick content checking (HasText(), IsCommand(), IsCallback()), which automatically find the message inside the update.
A Handler in Telegnom is a configurable unit consisting of:
- Logic (
HandlerFunc): What to do. - Trigger (
OnMessage,OnCallback...): When to do it. - Filters: Clarifying conditions (e.g., only commands).
- Local Middleware: Logic "around" the handler.
- Order Matters: The router checks handlers in the order they were registered. The first matching handler stops further search (First Match Wins).
- Registration before Startup: All handlers and groups must be fully configured before calling
StartPolling()orStartWebhook(). Dynamic addition of handlers after startup is not supported.
// 1. Register specific handlers first
b.Handler(handleStart).OnMessage(filters.Command("start"))
// 2. Then more general ones
b.Handler(echoHandler).OnMessage(filters.HasText)
// 3. (Optional) Handler with local middleware
b.Handler(secretHandler).OnMessage(filters.Command("admin")).Use(AuthMiddleware)
// 4. And only at the very end start the bot
b.StartPolling()Tip: If you use multiple triggers (e.g.,
.OnMessage().OnEditedMessage()), the handler will be registered in the corresponding processing queues.
See detailed examples (including Dependency Injection and multi-triggers) in examples/handlers/README.md.
Filters determine whether a handler should be executed. They work on an "all or nothing" principle: if you passed multiple filters, all of them must return true.
In Telegnom, the filtering system is built hierarchically (from high-level to low-level):
filtersPackage: Ready-made functions for registration (f.Command("start"),f.Text("hello")) and aliases to update methods (f.HasPhoto,f.IsPrivate).types.UpdateMethods: Logic related to the update itself (tg.Update.HasText(),tg.Update.IsCallback()). Convenient as they find the necessary message inside the update themselves.types.MessageMethods: Checks for specific message fields (msg.IsCommand(),msg.HasVideo()).- Custom Filters: Any function with the signature
func(tg *bot.Context) bool.
Detailed guide and examples of all types of filters are in examples/filters/README.md.
// Example using different levels of filtering
b.Handler(h1).OnMessage(filters.Command("start")) // filters package
b.Handler(h2).OnMessage(func(tg *bot.Context) bool {
return tg.Update.HasPhoto() // Update methods
})Groups allow you to combine multiple handlers and apply common middleware to them (e.g., for authorization, logging, or access restriction).
// Create a group
adminGroup := b.Group()
// Apply middleware only to this group
adminGroup.Use(AdminCheckMiddleware)
// All handlers in the group automatically inherit the middleware
adminGroup.Handler(adminStats).OnMessage(filters.Command("stats"))
adminGroup.Handler(adminBan).OnMessage(filters.Command("ban"))Important: Groups cannot be nested. Filters are applied only at the level of specific handlers within the group.
Details and an example of implementing admin panel protection can be found in examples/groups/README.md.
Middleware allows executing code BEFORE and AFTER event processing. Telegnom uses an "onion" architecture:
- Global (
b.Use): For all events. - Group (
group.Use): For handlers in a group. - Local (
handler.Use): For a specific single handler.
func MyMiddleware(next bot.HandlerFunc) bot.HandlerFunc {
return func(tg *bot.Context) {
// Code BEFORE handler (IN)
next(tg)
// Code AFTER handler (OUT)
}
}Stopping the chain: If you do not call
next(tg)and return, execution stops, and the handler will not be called. This is ideal for authorization.
Detailed examples and description of data passing via context can be found in examples/middleware/README.md.
Understanding how Telegnom processes each update is critical for writing predictable code. The entire process can be visualized as a pipeline.
[ Telegram Update ]
β¬
[ Global Middleware (IN) ] <-- (1. Global Layer)
β¬
(Router looks for handler) ββ
β¬ β (if filters don't match,
[ Filters Check ] ββββββββββββ look for next handler)
β¬
(Handler found!) <-- (2. Decision Point)
β¬
[ Group Middleware (IN) ] <-- (3. Group Layer)
β¬
[ Local Middleware (IN) ] <-- (4. Local Layer)
β¬
βββββββββββββββββββββββββ
β HANDLER FUNCTION β <-- (5. Your Code)
βββββββββββββββββββββββββ
β¬
[ Local Middleware (OUT) ]
β¬
[ Group Middleware (OUT) ]
β¬
[ Global Middleware (OUT) ]
Since middleware works on the "matryoshka" (or onion) principle, the execution order looks like this. Imagine we have logging at each level:
[Global] Started processing update #100
[Group] Checked Admin rights... OK
[Local] Prepared user context
>>> HANDLER: Executing logic for /start command
[Local] Cleanup context
[Group] Saved admin stats
[Global] Finished processing (Took 2ms)
You can stop processing at different stages, but the semantics will differ:
Filters are checked before launching group middleware and the handler itself.
- If a filter returns
false-> The router simply looks for the next suitable handler. - Scenario: "This command is only available in private chats". If written in a group β the bot simply ignores it (or finds another handler).
Middleware starts when a handler is already found.
- If middleware does not call
next(tg)-> Processing stops completely. No other handlers are searched. - Scenario: "Command found, but user has no rights". Middleware writes "Access denied" and returns.
Telegnom supports two ways of receiving updates: Long Polling and Webhook. Telegram Documentation.
- Long Polling: The bot periodically polls Telegram servers for new events. This is the simplest way, not requiring an external IP address or SSL certificate. Ideal for local development and small bots.
- Webhook: Telegram sends an HTTP request to your server as soon as an event occurs. This method is more efficient and responds faster to messages but requires a public URL and configured HTTPS. Recommended for high-load projects.
The StartPolling() method starts an infinite loop of polling the Telegram API for updates. It is blocking β program execution stops until interruption (e.g., Ctrl+C).
Options (optional) for Long Polling (TG API DOC):
WithPollLimit(limit int): Sets the maximum number of updates retrieved per request (from 1 to 100).WithPollOffset(offset int64): Sets the initial update ID for polling. Positive value β start from a specific ID; negative (e.g., -1) β get only the last update.WithPollTimeout(d time.Duration): Sets the timeout for a single polling request.WithAllowedUpdates(ut ...types.UpdateType): Sets the types of updates the bot will receive.
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"github.com/stalkerxxl/telegnom/bot"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
token := os.Getenv("BOT_TOKEN")
// 2. Initialize bot
b, err := bot.NewBot(ctx, token, bot.WithPanicRecovery())
if err != nil {
log.Fatalf("Error creating bot: %v", err)
}
// 3. Register handlers
b.Handler(func(tg *bot.Context) {
tg.Bot.SendMessage(&bot.SendMessageParams{
ChatID: tg.ChatID(),
Text: "Running in Long Polling mode!",
})
}).OnMessage()
// 4. Start infinite polling loop
log.Println("Bot started in Long Polling mode...")
b.StartPolling()
}
bot.StartPolling()will return an error if an outgoing webhook is configured. We recommend callingbot.DeleteWebhook()before starting.
In Webhook mode, Telegram sends updates to your server itself. This eliminates delays and saves resources. Telegnom offers three ways to work with webhooks:
- Built-in HTTP Server:
bot.StartWebhook(":8080")β simplest way (usually used behind Nginx | Ngrok). - Built-in HTTPS Server:
bot.StartWebhookTLS(":443", "cert.pem", "key.pem")β if the bot faces the internet directly. - External Server:
bot.WebhookHandler()β for integration into an existing web application (Gin, Echo, etc.).
Important: Before starting the bot in this mode, you must manually call
tg.Bot.SetWebhook()(once when changing mode or parameters).
package main
import (
"context"
"log"
"os"
"github.com/stalkerxxl/telegnom/bot"
)
func main() {
ctx := context.Background()
token := os.Getenv("BOT_TOKEN")
// 1. Initialize bot with webhook configuration
webhookURL := "https://my-domain.com/secret-path"
secretToken := "my-secret-token"
b, _ := bot.NewBot(ctx, token,
bot.WithWebhookURL(webhookURL),
bot.WithWebhookSecretToken(secretToken),
)
// 2. Register webhook in Telegram (done once or when settings change)
_, err := b.SetWebhook(&bot.SetWebhookParams{
URL: webhookURL,
SecretToken: secretToken,
})
if err != nil {
log.Fatalf("Error registering webhook: %v", err)
}
// 3. Register handlers
b.Handler(func(tg *bot.Context) {
tg.Bot.SendMessage(&bot.SendMessageParams{
ChatID: tg.ChatID(),
Text: "Received via Webhook!",
})
}).OnMessage()
// 3. Start built-in server
log.Println("Bot started in Webhook mode on port :8080...")
if err := b.StartWebhook(":8080"); err != nil {
log.Fatalf("Server error: %v", err)
}
}For sending files (photos, videos, documents), the types.InputFile structure is used. You can send files by URL, FileID, disk path, or from io.Reader.
Detailed documentation: docs/InputFile.md.
// Sending photo from disk
_, _ = tg.Bot.SendPhoto(&bot.SendPhotoParams{
ChatID: tg.ChatID(),
Photo: &types.InputFile{FilePath: "images/cat.jpg"},
Caption: "Kitty",
})In Telegnom, error handling is divided into two levels: system errors and Telegram API errors. This is done so you can flexibly react to network problems separately from bot logic errors.
- System Errors: Internet connection issues, DNS, JSON parsing errors. These errors are returned as standard Go errors.
- Telegram API Errors: Errors returned by Telegram itself (e.g., "Chat not found", "Bot was blocked by the user", "Flood control exceeded"). They are returned as type
*bot.TGError.
In Go, use errors.As to check the error type.
package main
import (
"errors"
"fmt"
_ "time"
"github.com/stalkerxxl/telegnom/bot"
)
func myHandler(tg *bot.Context) {
_, err := tg.Bot.SendMessage(&bot.SendMessageParams{
ChatID: tg.Update.Chat.ID,
Text: "Hello!",
})
if err != nil {
var tgErr *bot.TGError
if errors.As(err, &tgErr) {
// This is an error from Telegram itself
fmt.Printf("API Error: %d - %s\n", tgErr.Code, tgErr.Description)
// Special convenience methods:
if tgErr.IsFlood() {
retryAfter := tgErr.GetRetryAfter()
fmt.Printf("Need to wait %d seconds\n", retryAfter)
}
if newChatID, ok := tgErr.MigrateTo(); ok {
fmt.Printf("Chat migrated to supergroup with ID: %d\n", newChatID)
}
} else {
// This is a system error (network, proxy, etc.)
fmt.Printf("System Error: %v\n", err)
}
}
}By default, the library uses a built-in handler that logs system errors to the console via log.Printf. You can replace it with your own or disable it completely.
- Default: Enabled (built-in logger).
- Custom Handler: Pass a function to
bot.WithErrorHandler(myFunc). - Disable: Pass
niltobot.WithErrorHandler(nil).
Using a custom handler is useful for sending reports to Sentry, writing to files, or notifying an admin in Telegram.
opts := []bot.Option{
// Custom handler
bot.WithErrorHandler(func (err error) {
log.Printf("Critical error: %v", err)
}),
}
// Or disable (logs will not be output)
// bot.WithErrorHandler(nil),
b, _ := bot.NewBot(ctx, token, opts...)Important: API errors (
TGError) do not fall intoErrorHandlerautomatically when calling methods likeSendMessage. They are returned to you directly for manual handling.ErrorHandlerreceives network-level errors and errors that occur inside the framework itself (e.g., when receiving updates).
Telegnom has built-in debugging tools to simplify development and monitoring.
If your handler is not triggering, it is likely due to filters. To see exactly what data is coming from Telegram, use WithDebugHandler.
opts := []bot.Option{
// Use built-in logger (outputs Update in JSON)
bot.WithDebugHandler(nil), // nil disables standard handler
// Or write your own
bot.WithDebugHandler(func(tg *bot.Context) {
log.Printf("ID: %d, Text: %s", tg.Update.ID, tg.Update.Message.Text)
}),
}To prevent a single error in handler code from crashing the entire bot, always use WithPanicRecovery(). It intercepts panics, logs them via ErrorHandler, and allows the bot to continue working.
b, _ := bot.NewBot(ctx, token, bot.WithPanicRecovery())Coming Soon...
Brief overview of Telegnom source code organization:
bot/: π§ Framework core. Contains bot logic, context management, and routing.methods.go: π οΈ Implementation of all Telegram Bot API methods (SendMessage,GetMe, etc.).methods_params.go: π Parameter structures for API methods (e.g.,SendMessageParams).
types/: π Data types. Full set of structures corresponding to Telegram Bot API objects (Update, Message, User, etc.).filters/: π Package with built-in filters for flexible handler trigger configuration.examples/: π Usage examples. Ready-made recipes for working with middleware, groups, webhooks, and more.docs/: π Documentation. Additional materials and specifications for individual components.
Coming Soon...
Coming Soon...
Coming Soon...
Coming Soon...