go-telegram logo

A fully-generated,
strongly-typed Go client
for the Telegram Bot API

176 methods. 1408 generated tests. Zero any in the public surface.

176 methods · 301 types · 1,408 tests · MIT licensed
echo_bot.go
package main

import (
    "context"
    "log"
    "os"

    "github.com/lukaszraczylo/go-telegram/api"
    "github.com/lukaszraczylo/go-telegram/client"
    "github.com/lukaszraczylo/go-telegram/dispatch"
)

func main() {
    bot, _ := client.NewRetryDoer(os.Getenv("BOT_TOKEN"), nil)
    d := dispatch.New(bot)

    d.OnMessage(func(ctx context.Context, msg *api.Message) {
        bot.SendMessage(ctx, &api.SendMessageParams{
            ChatID: api.ChatIDFromInt(msg.Chat.ID),
            Text:   msg.Text,
        })
    })

    if err := d.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Features

Built for correctness, composability, and production reliability

Generated from the live docs

IR + emitter pipeline runs weekly; a PR opens automatically for any Telegram-side change.

No any in the public API

Typed unions — ChatID, MessageOrBool — sealed interfaces with marker methods and auto-decode.

Pluggable transport + codec

Drop in fasthttp, sonic, or goccy/go-json with a one-line swap.

Production-ready out of the box

Retry middleware honouring retry_after, panic recovery, structured errors with sentinels.

Typed dispatcher

Generic Handler[T], composable filters, conversation handler with pluggable storage, per-update goroutine pool.

Self-verifying codegen

Every regen runs scrape → audit → emit → 1408 generated tests. Nothing ships without passing.

Comparison

How go-telegram compares to other Go Telegram bot libraries

go-telegram

HTML scraper · this library

Generated from spec (HTML)
Typed unions, no any
1,408 auto-generated tests
Conversation handler
Retry middleware (retry_after)
Pluggable JSON codec
gtb

gotgbot/v2

JSON spec · popular library

Generated from spec (JSON)
Partial typed unions
Auto-generated tests
Conversation handler
User-implemented retry
Pluggable JSON codec
tba

telegram-bot-api/v5

Hand-coded

Generated from spec
Typed unions
Auto-generated tests
Conversation handler
Retry middleware
Pluggable JSON codec

Installation

One command. No CGO, no system deps.

go get

Requires Go 1.21+

go get github.com/lukaszraczylo/go-telegram

pkg.go.dev

Full API reference

View on pkg.go.dev

Markdown reference

Auto-generated, browse on GitHub

Browse reference docs

Telegram Bot API reference

Upstream spec this library tracks

core.telegram.org/bots/api

Usage

Echo bot — the minimal working example

echo_bot.go
package main

import (
    "context"
    "log"
    "os"

    "github.com/lukaszraczylo/go-telegram/api"
    "github.com/lukaszraczylo/go-telegram/client"
    "github.com/lukaszraczylo/go-telegram/dispatch"
)

func main() {
    // NewRetryDoer wraps the default transport with retry middleware
    // that honours Telegram's retry_after field automatically.
    bot, err := client.NewRetryDoer(os.Getenv("BOT_TOKEN"), nil)
    if err != nil {
        log.Fatal(err)
    }

    d := dispatch.New(bot)

    // Handler[T] is generic — the type parameter is the concrete update type.
    d.OnMessage(func(ctx context.Context, msg *api.Message) {
        _, err := bot.SendMessage(ctx, &api.SendMessageParams{
            // ChatIDFromInt returns a typed ChatID — no interface{} here.
            ChatID: api.ChatIDFromInt(msg.Chat.ID),
            Text:   msg.Text,
        })
        if err != nil {
            log.Printf("send error: %v", err)
        }
    })

    if err := d.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Codegen pipeline

The emitter scrapes the live Telegram Bot API docs (HTML), builds an intermediate representation (api.json), then generates Go code and tests. An audit step validates every method signature against the IR before emission.

HTML docs
IR (api.json)
audit
Go code + tests
make snapshot && make regen

Examples

14 runnable bots covering the most common patterns

Advanced

Power-user patterns — expand to read

Conversation flows

The conversation handler chains multiple message steps and stores state between them. Any storage backend implementing the StateStorage interface works — in-memory, Redis, Postgres.

conv := conversation.New(storage)

conv.AddState("ask_name", func(ctx context.Context, msg *api.Message) (string, error) {
    bot.SendMessage(ctx, &api.SendMessageParams{
        ChatID: api.ChatIDFromInt(msg.Chat.ID),
        Text:   "What's your name?",
    })
    return "ask_age", nil
})

conv.AddState("ask_age", func(ctx context.Context, msg *api.Message) (string, error) {
    name := conv.GetData(ctx, "name")
    // ... handle age input
    return conversation.Done, nil
})

d.OnMessage(conv.Handler("start"))

Custom filters

Filters compose with And, Or, Not. A filter is just a function func(*api.Update) bool.

// Only handle private messages from admins
adminOnly := filters.And(
    filters.IsPrivate,
    filters.UserIDIn(adminIDs...),
)

d.OnMessage(func(ctx context.Context, msg *api.Message) {
    // handler body
}, adminOnly)

// Custom filter — any function works
isLong := func(u *api.Update) bool {
    return u.Message != nil && len(u.Message.Text) > 200
}
d.OnMessage(handleLongMsg, isLong)

Custom HTTP / JSON codec

Pass a transport.Options to swap the HTTP client or JSON codec. Useful for squeezing throughput on high-volume bots.

import (
    "github.com/lukaszraczylo/go-telegram/client"
    "github.com/lukaszraczylo/go-telegram/transport"
    jsoniter "github.com/json-iterator/go"
)

opts := &client.Options{
    Transport: transport.NewFasthttpTransport(nil),
    Codec:     transport.JSONCodec(jsoniter.ConfigCompatibleWithStandardLibrary),
}

bot, err := client.NewRetryDoer(token, opts)