# How to Use Middleware in Telego Handlers: A Complete Guide

> Master middleware in Telego handlers to add reusable logic like logging and timeouts. This guide shows how ctx.Next(update) intercepts updates for powerful handler composition.

- Repository: [Artem Yadelskyi/telego](https://github.com/mymmrac/telego)
- Tags: how-to-guide
- Published: 2026-03-07

---

**Telego implements middleware as `Handler` functions that intercept updates via `ctx.Next(update)`, allowing you to compose reusable logic like logging, recovery, and timeouts that execute before your main handlers.**

The Telego library (`mymmrac/telego`) provides a robust handler system for building Telegram bots in Go. Its middleware architecture follows a chain-of-responsibility pattern where each middleware can modify the context, terminate execution, or pass control to the next layer.

## Understanding the Middleware Pattern in Telego

In Telego, middleware is not a separate interface but rather the same `Handler` type used for final update processing. This unified design simplifies the API while enabling powerful composition patterns.

### The Handler Signature

Every middleware function conforms to the `Handler` signature defined in [`telegohandler/handler_group.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/handler_group.go):

```go
func(ctx *telegohandler.Context, update telego.Update) error

```

The `*Context` parameter provides access to the bot instance, the current update, and control methods for the execution chain. When your middleware finishes its work, it returns an `error` to signal whether processing should continue or halt.

### The Chain of Responsibility

Middleware executes sequentially based on registration order. The critical mechanism that enables this chaining is the `ctx.Next(update)` method found in [`telegohandler/context.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/context.go).

When your middleware calls `ctx.Next(update)`, it yields control to the next middleware in the chain or the final handler. If your middleware returns without invoking `Next`, the chain terminates immediately—subsequent middlewares and handlers never execute. This behavior allows middleware to act as gatekeepers, short-circuiting requests that fail authentication, rate limiting, or validation checks.

## Registering Middleware with Use

You attach middleware to the handler system through the `Use` method available on `BotHandler` and handler groups. According to [`telegohandler/bot_handler.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/bot_handler.go) and [`telegohandler/handler_group.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/handler_group.go), this method appends middleware to an ordered slice that the execution engine traverses during update processing.

The `Use` signature accepts variadic `Handler` arguments:

```go
func (h *BotHandler) Use(middlewares ...Handler)

```

### Global Middleware Registration

To apply middleware to every incoming update, register it on the base `BotHandler` instance:

```go
handler, _ := telegohandler.NewBotHandler(bot, updates)

// Apply to all updates
handler.Use(
    telegohandler.PanicRecovery(),
    CustomLoggingMiddleware,
)

```

Registration order determines execution order. In this example, `PanicRecovery` executes first, then `CustomLoggingMiddleware`, and finally the matched handler.

## Built-in Middleware Options

Telego provides production-ready middleware implementations in [`telegohandler/middleware.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/middleware.go) that handle common cross-cutting concerns.

### PanicRecovery

The `PanicRecovery` middleware catches panics within the handler chain, logs the stack trace, and prevents the bot from crashing. It returns the panic as an error to the caller, allowing the update processing loop to continue with subsequent updates.

```go
handler.Use(telegohandler.PanicRecovery())

```

This is essential for production deployments where unhandled panics in one update handler could otherwise terminate the entire bot process.

### Timeout

The `Timeout` middleware enforces a maximum execution duration for the entire handler chain. It wraps the context with a deadline, ensuring that slow handlers or hanging external calls do not block the update processing pipeline indefinitely.

```go
handler.Use(telegohandler.Timeout(5 * time.Second))

```

When the timeout expires, the context cancellation propagates through the chain, allowing handlers to abort database queries or HTTP requests gracefully.

## Creating Custom Middleware

Custom middleware follows the same `Handler` signature as final handlers but utilizes `ctx.Next` to continue the chain. This pattern enables you to inject logging, metrics, authentication, or transformation logic.

### Logging and Timeout Middleware Example

The following implementation demonstrates a middleware that logs update IDs and applies a 2-second timeout to subsequent processing, adapted from the patterns in [`telegohandler/context.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/context.go):

```go
func LogAndTimeout(next telegohandler.Handler) telegohandler.Handler {
    return func(ctx *telegohandler.Context, upd telego.Update) error {
        // Log incoming update
        ctx.Bot().Logger().Infof("Processing update %d", upd.UpdateID)

        // Apply timeout to the rest of the chain
        ctx, cancel := ctx.WithTimeout(2 * time.Second)
        defer cancel()

        // Continue to next middleware or handler
        return next(ctx, upd)
    }
}

```

Register this middleware using the `Use` method to apply it globally, or attach it to specific handler groups for targeted protection.

### Conditional Short-Circuiting

Middleware can terminate the chain early by returning without calling `next`. This is useful for rate limiting or access control:

```go
func RateLimit(next telegohandler.Handler) telegohandler.Handler {
    return func(ctx *telegohandler.Context, upd telego.Update) error {
        if isRateLimited(upd.Message.From.ID) {
            // Stop chain execution; handler never runs
            return nil
        }
        return next(ctx, upd)
    }
}

```

## Scoped Middleware with Handler Groups

Telego supports hierarchical handler groups that allow middleware to apply only to specific subsets of handlers. The `Group` method, defined in [`telegohandler/handler_group.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/handler_group.go), creates a new `HandlerGroup` that inherits middleware from its parent but can register its own.

### Creating Isolated Groups

```go
// Create a group for admin commands
adminGroup := handler.Group()
adminGroup.Use(AdminAuthMiddleware)

// This handler runs with AdminAuthMiddleware
adminGroup.Handle(func(ctx *telegohandler.Context, upd telego.Update) error {
    // Admin-only logic
    return ctx.Bot().SendMessage(telego.SendMessageParams{
        ChatID: upd.Message.Chat.ID,
        Text:   "Admin command executed",
    })
})

```

### Middleware Execution Order

When processing an update, the execution order follows the group's nesting hierarchy:

1. Global middleware (registered on base `BotHandler`)
2. Parent group middleware
3. Child group middleware
4. Final handler

This cascading structure allows you to compose reusable security, logging, and transformation layers that apply only where needed without polluting the global namespace.

## Summary

- **Middleware is a Handler**: Telego treats middleware as standard `Handler` functions with the signature `func(ctx *telegohandler.Context, update telego.Update) error`, unifying the API surface.
- **Chaining via Next**: Middleware controls execution flow through `ctx.Next(update)`; omitting this call short-circuits the chain, preventing subsequent handlers from running.
- **Registration with Use**: Attach middleware globally or to specific groups using the `Use` method, which appends handlers to an ordered slice processed sequentially.
- **Built-in Protection**: Leverage `PanicRecovery` and `Timeout` from [`telegohandler/middleware.go`](https://github.com/mymmrac/telego/blob/main/telegohandler/middleware.go) to harden production bots against crashes and hanging operations.
- **Hierarchical Scoping**: Create isolated middleware layers using `Group()` to apply authentication, logging, or rate limiting only to specific handler subsets without affecting global routing.

## Frequently Asked Questions

### How does middleware execution order work in Telego?

Middleware executes in the exact order it was registered via the `Use` method. When an update arrives, the `Context.Next` method iterates through this ordered slice, invoking each middleware sequentially. If a middleware calls `ctx.Next(update)`, control passes to the next element; if it returns without calling `Next`, the chain terminates immediately and no subsequent middleware or handlers execute.

### What is the difference between a middleware and a handler in Telego?

There is no structural difference—both conform to the `Handler` signature `func(ctx *telegohandler.Context, update telego.Update) error`. The distinction is behavioral: middleware is designed to intercept updates and delegate to the next layer via `ctx.Next(update)`, while final handlers typically process the update directly without calling `Next`. This unified design allows the same function to act as middleware in one context and a final handler in another.

### Can I apply middleware to only specific commands or message types?

Yes, through the handler group hierarchy. Instead of registering middleware globally on the `BotHandler` instance, create a subgroup using the `Group()` method and attach middleware to that specific group using `Use`. Only handlers registered within that group or its descendants will execute that middleware. This pattern is ideal for isolating authentication, administrative checks, or specialized logging to specific command subsets.

### How do I prevent a handler from running if middleware validation fails?

Return from the middleware without invoking `ctx.Next(update)`. When middleware returns `nil` or an error without calling `Next`, the `Context.Next` execution engine stops traversing the chain immediately. Subsequent middlewares and the final handler never receive the update. This short-circuit pattern is commonly used for rate limiting, IP blocking, or permission denial, where the middleware consumes the update and optionally sends an error message to the user.