A fast, idiomatic Go client for the Telegram MTProto API.
Note: mtgo stands for MTProto Go. It is a Telegram client library and has no relation to Magic: The Gathering Online.
- Full MTProto 2.0 — encryption, key generation, CDNs, file transfers
- High-level client API — messages, chats, media, inline, stories, payments, business connections
- Handler-based updates — register handlers with filters, priorities, and handler groups
- Middleware — invoker-level (RPC calls) and handler-level (update dispatch) middleware chains
- Plugin system — lifecycle plugins with
Start/Stophooks - Multi-client — run multiple bots/users in one process with
ComposeorIdle - Session import/export — Telethon, Pyrogram, GramJS, mtcute, tdesktop session formats
- Storage backends — SQLite, PostgreSQL, MongoDB, or bring your own adapter
- MTProxy support — dd/ee/simple secrets with obfuscated2 and fake TLS
- WebSocket transport — MTProto over WebSocket for restrictive networks
- Auto-reconnect — exponential backoff with jitter and configurable max attempts
- Health checks — periodic ping/pong keepalive with configurable timeout
- Generated TL layer — auto-generated from Telegram schemas; update with one command
- Pure Go — no CGO required (SQLite via modernc.org/sqlite)
go get github.com/mtgo-labs/mtgopackage main
import (
"fmt"
"log"
"os"
"github.com/mtgo-labs/mtgo/telegram"
"github.com/mtgo-labs/mtgo/telegram/types"
)
func main() {
client, err := telegram.NewClient(apiID, apiHash, &telegram.Config{
BotToken: os.Getenv("BOT_TOKEN"),
SessionName: "my_bot",
SavePeers: true,
})
if err != nil {
log.Fatal(err)
}
client.OnMessage(func(client *telegram.Client, msg *types.Message) {
msg.Reply(msg.Text)
}, telegram.Private)
if err := client.Connect(0); err != nil {
log.Fatal(err)
}
defer client.Stop()
fmt.Println("bot is running")
client.Idle()
}See examples/ for more: middleware, conversations, SQLite, MongoDB, keyboards, media, MTProxy, webapp, and more.
client, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{
BotToken: "123456:ABC-DEF",
})client, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{
PhoneNumber: "+1234567890",
})
client.Connect(0)
// Terminal prompts for code/password automaticallyclient, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{})
client.QRLogin(context.Background())
// Displays QR code link for Telegram mobile scanningImport existing sessions from other frameworks:
import "github.com/mtgo-labs/mtgo/session"
// From Telethon
client, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{
SessionString: session.MustTelethon("1BVusO..."),
})
// From Pyrogram
client, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{
SessionString: session.MustPyrogram("BAAJbwI..."),
})// Command handler
client.OnMessage(func(client *telegram.Client, msg *types.Message) {
msg.Reply("Welcome!")
}, telegram.Command("start"))
// Regex filter
client.OnMessage(func(client *telegram.Client, msg *types.Message) {
msg.Reply("Got a number!")
}, telegram.Regex(`\d+`))
// Combined filters
client.OnMessage(handlePrivate, telegram.Private.And(telegram.HasText))
// Callback queries
client.OnCallbackQuery(func(client *telegram.Client, cb *types.CallbackQuery) {
cb.Answer("Pressed!", false)
})Built-in filters: Private, Group, HasText, Command, Regex, Media, Photo, Video, Document, Audio, Voice, VideoNote, Sticker, Animation, Contact, Location, Venue, Poll, Game, Forwarded, Reply, Outgoing, and composable with .And(), .Or(), .Not().
Two middleware levels for different concerns:
| Level | Method | Intercepts | Use case |
|---|---|---|---|
| Invoker | UseInvokerMiddleware |
Outgoing RPC calls | Rate limiting, flood wait, logging, metrics |
| Handler | UseMiddleware |
Incoming update dispatch | Auth, i18n, conversation state |
// Invoker middleware (wraps RPC calls)
mw := floodwait.New()
client.UseInvokerMiddleware(mw.Middleware())
// Handler middleware (wraps update dispatch)
client.UseMiddleware(authMiddleware, -10) // lower priority = outermost
client.UseMiddleware(loggingMiddleware, 0)An invoker middleware wraps tg.Invoker to intercept, inspect, and modify RPC calls:
import (
"context"
"io"
"github.com/mtgo-labs/mtgo/tg"
)
// SilentMiddleware forces all outgoing messages to be silent.
func SilentMiddleware() telegram.InvokerMiddleware {
return func(next tg.Invoker) tg.Invoker {
return tg.InvokerFunc(func(ctx context.Context, input tg.TLObject, decode func(io.Reader) (tg.TLObject, error)) (tg.TLObject, error) {
// Type-assert and modify request parameters before the call
if req, ok := input.(*tg.MessagesSendMessageRequest); ok {
req.Silent = true
req.SetFlags() // required after modifying flag-controlled fields
}
return next.RPCInvoke(ctx, input, decode)
})
}
}
// Register it (first = outermost)
client.UseInvokerMiddleware(SilentMiddleware())Ready-made middlewares: floodwait, ratelimit.
Plugins implement the Plugin interface to add lifecycle-aware, modular functionality:
import (
"context"
"log"
"github.com/mtgo-labs/mtgo/telegram"
)
// MetricsPlugin tracks bot usage.
type MetricsPlugin struct {
client *telegram.Client
messageCount int64
}
func (p *MetricsPlugin) Name() string { return "metrics" }
func (p *MetricsPlugin) Start(ctx context.Context, client *telegram.Client) error {
p.client = client
log.Printf("[%s] started", p.Name())
return nil
}
func (p *MetricsPlugin) Stop(ctx context.Context) error {
log.Printf("[%s] stopped — processed %d messages", p.Name(), p.messageCount)
return nil
}// Register plugins before connecting
client.Use(i18nPlugin)
client.Use(conversationsPlugin)
client.Use(&MetricsPlugin{})
// Plugins start/stop automatically with the client
client.Connect(0)Available plugins: conversations, i18n.
import (
"github.com/mtgo-labs/storage"
"github.com/mtgo-labs/storage/sqlite"
)
ext, _ := sqlite.Open("session.db")
defer ext.Close()
client, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{
BotToken: botToken,
SessionName: "my_bot",
Storage: storage.NewAdapter(ext),
})import (
"github.com/mtgo-labs/storage"
"github.com/mtgo-labs/storage/postgres"
)
ext, _ := postgres.Open(postgres.Config{
Host: "localhost",
Port: 5432,
User: "postgres",
Password: os.Getenv("PG_PASSWORD"),
Database: "mtgo_sessions",
SSLMode: "disable",
})
defer ext.Close()
client, _ := telegram.NewClient(apiID, apiHash, &telegram.Config{
BotToken: botToken,
SessionName: "my_bot",
Storage: storage.NewAdapter(ext),
})Backends: SQLite, PostgreSQL, MongoDB. See the storage repo for custom adapter docs.
Each telegram.NewClient() call creates a separate, independent mtgo client instance
(a Telegram bot or user session). Run multiple bots, user accounts, or a mix of both
in a single process and block until all stop:
// Create independent MTGO client instances
bot1, err := telegram.NewClient(apiID, apiHash, &telegram.Config{
BotToken: token1,
SessionName: "bot1",
})
if err != nil {
log.Fatal(err)
}
bot2, err := telegram.NewClient(apiID, apiHash, &telegram.Config{
BotToken: token2,
SessionName: "bot2",
})
if err != nil {
log.Fatal(err)
}
// Register handlers per client
bot1.OnMessage(func(ctx *telegram.Context) {
ctx.Reply("Bot 1 here!")
}, telegram.Private)
bot2.OnMessage(func(ctx *telegram.Context) {
ctx.Reply("Bot 2 here!")
}, telegram.Private)
// Connect both clients
bot1.Connect(0)
bot2.Connect(0)
// Compose starts all clients in goroutines and blocks until any stops.
// It returns an error if any client fails to start.
if err := telegram.Compose(bot1, bot2); err != nil {
log.Fatal(err)
}
// Alternatively, call telegram.Idle() to block until ALL registered
// clients stop (every NewClient auto-registers).cmd/ Code generators (TL schema, error types)
compiler/ TL compiler and templates
docs/ API reference
examples/ Working examples
internal/ MTProto internals (crypto, session, transport)
session/ Session string import/export
tg/ Generated TL types (do not edit directly)
tgerr/ Generated error types
telegram/ High-level client API, handlers, filters, middleware
mtproxy/ MTProxy obfuscated2/fake-TLS transport
# Regenerate TL types from schema
go run cmd/tlgen/main.go
# Regenerate error types
go run cmd/errgen/main.goDo not edit generated files directly. Modify the schema in compiler/ or cmd/ instead.
See CONTRIBUTING.md for development setup, code style, and PR process.
See SECURITY.md for reporting vulnerabilities.
Licensed under the Apache License 2.0.
| Repository | Description |
|---|---|
| storage | Persistent storage adapters (SQLite, PostgreSQL, MongoDB) |
| plugins | Official plugins (conversations, i18n) |
| middlewares | Invoker middleware (flood wait, rate limiting) |
| plugins-template | Template for new plugins |
| middlewares-template | Template for new middlewares |