Library / SDK
zelenin/go-tdlib avatar
zelenin/go-tdlib

go-tdlib wraps TDLib in Go and regenerates its bindings from the TL schema

Go wrapper for TDLib (Telegram Database Library)

543 stars112 forksGoMIT

At a glance

What is it?
go-tdlib is a Go binding for the Telegram Database Library with a code generator that turns the TL schema into typed requests, results and update handlers, plus authorizers for user, bot and QR login. It suits teams building a Telegram client or bot backend in Go.
Who is it for?
Adopt go-tdlib if you are writing a Telegram client or bot backend in Go and want typed TDLib requests instead of hand written JSON calls, and you are willing to compile TDLib from source at a pinned commit. Do not adopt it if you only need to send messages through a bot token, since the Telegram Bot API needs no local library at all.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 168 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What TDLib is, and why the Go binding is not thin

TDLib is the Telegram Database Library, the official client library from the Telegram team, and go-tdlib is a Go wrapper for it with full support of TDLib. The word wrapper undersells it. Telegram's own libraries are C++, and the alternative for a Go developer was to run a C++ process, speak JSON to it over a pipe and write the request and response structures by hand. A Go binding that exposes TDLib's API as typed functions removes the second half of that work, and the generated code is the part that matters.

The library is pinned to a specific TDLib commit rather than to a version tag. The project states that the currently supported version of TDLib corresponds to the commit hash 49b3bcb, updated on 2026-05-08, and the installation notes tell you to check out that supported commit when building TDLib. Pinning by commit hash is a reasonable choice here because TDLib's schema changes frequently, and a floating version would mean the generated Go types could disagree with the library you actually linked against. The last push to this repository was on 2026-05-09, one day after the TDLib commit date, which suggests the wrapper is regenerated to follow upstream rather than developed ahead of it.

The audience is specific: developers building a Telegram client, a bot backend, or an automation tool in Go who want the full Telegram API surface, including the user account features that the Bot API does not expose. The repository is MIT licensed and there are no GitHub releases, so you consume the master branch or a commit hash.

The generator, which is the real product

The Makefile at the root is short and it tells you how the bindings are produced. A tag variable holds the TDLib commit hash, and three targets do the work. The schema-update target curls the TL schema file from the Telegram repository at that tag into the local data directory:

bash
	curl https://raw.githubusercontent.com/tdlib/td/${TAG}/td/generate/scheme/td_api.tl 2>/dev/null > ./data/td_api.tl

The generate-json target then runs a Go program to turn that schema into a JSON description, and generate-code runs another program that writes the Go source into the client package, with separate file names for the functions, the types, the unmarshaler and the version constant, followed by go fmt. The layout of the repository follows this pipeline: a client/ directory holds the generated and hand written client code, cmd/ holds the generator programs, data/ holds the TL schema and the generated json-schema, internal/ holds shared code, and iter/ holds iteration helpers.

That last point has a practical consequence worth noting. The project says the package includes a .tl-parser and a generated json-schema for creating libraries in other languages, so the schema work is exposed rather than hidden inside the Go package. If you need a binding in a language the project does not cover, the schema and the parser are the starting point, and you are not limited to reading Go source to find out what the Telegram API looks like. It also means the binding surface is exactly the TDLib schema, which is a useful thing to know when you are trying to work out whether a method exists.

Building against a locally compiled TDLib

Because TDLib is a native library, the Go build needs cgo and needs to know where the headers and the library live. The project points at the official TDLib build instructions and tells you to check the option to install the built TDLib to /usr/local instead of placing the files into the td/tdlib directory. That single checkbox decides whether the default build works or whether you have to point every environment variable at your own tree.

If you install TDLib somewhere else, the include and library paths are set with environment variables, and the library is linked as tdjson:

shell
CGO_CFLAGS=-I/path/to/tdlib/include \
CGO_LDFLAGS="-Wl,-rpath,/path/to/tdlib/lib -L/path/to/tdlib/lib -ltdjson" \
go build ...

The rpath entry matters for deployment, because it tells the dynamic loader where to find the shared library at run time rather than only at link time. On Windows the same mechanism is expressed with full paths and the CGO_ENABLED flag set, and the PowerShell example puts all three variables on one line before the go build call. The documentation also notes that the DLL has to be copied next to the compiled executable to run it, which is the standard consequence of linking a native library on Windows rather than anything unusual about this project.

This is the practical cost of the approach, and it is not small. You are compiling a C++ library from source, matching its commit to the one the wrapper was generated against, and shipping native artefacts alongside your binary. The payoff is the typed API and the fact that TDLib handles the Telegram protocol, local database, file cache and secret chats, none of which a Bot API client does.

Authorizers: user, bot and QR login

The client package offers more than one way to authenticate, and the difference matters. The main example registers an application at my.telegram.org to get an api_id and api_hash, then sets the TDLib parameters: the database and files directories under a local .tdlib folder, flags for using the file, chat info and message databases, the API credentials, a system language code of en, and device model, system version and application version strings that identify your client to Telegram.

From there the code picks an authorizer. The client authorizer asks for a phone number and code, the bot authorizer takes a bot token instead, and a third option is the QR authorizer, which is the one that suits a server. The QR example passes a callback that receives a login link and writes it to a PNG file, using a QR code library to render it:

go
	authorizer := client.QrAuthorizer(tdlibParameters, func(link string) error {
		return qrcode.WriteFile(link, qrcode.Medium, 256, "qr.png")
	})

The QR flow is what makes a headless server workable, because there is no keyboard to type a code into. The bot authorizer is the closest to the Bot API in behaviour, and it is the one to reach for if you only need bot functionality, though at that point the question becomes whether you need TDLib at all.

The sample also shows a piece of operational hygiene that is easy to miss and expensive to skip. After creating the client, it reads the version and commit_hash options and compares the commit hash against the constant the library was built against, logging a warning when they differ. That single comparison catches the most common and most confusing failure in this setup, where the Go types and the native library are from different TDLib versions and the error you get is a mismatch in a request field rather than anything about versions.

Updates, proxies and the option plumbing

Receiving updates is done through a result handler passed as a client option at construction time, and the callback receives a typed value that you switch on by its type:

go
resHandCallback := func(result client.Type) {
    log.Printf("%#v", result.GetType())
}

tdlibClient, err := client.NewClient(authorizer, client.WithResultHandler(client.NewCallbackResultHandler(resHandCallback)))

Because the callback argument is the generated interface type, every update TDLib can send is reachable without hand parsing anything, and the GetType method gives you the discriminant to switch on. Updates are how a Telegram client stays current, since the library pushes new messages, chat states and call information as they happen rather than waiting for you to poll.

Proxy support is configured the same way, as another option to NewClient, using an AddProxyRequest with a server, a port, an enable flag and a typed proxy type such as ProxyTypeSocks5 carrying a username and password. Both features are options rather than separate constructors, which is a small but sensible API decision, since a client can want a proxy, an update handler, or both.

The library also exposes the raw TDLib functions, so calls like SetLogVerbosityLevel and GetOption are available with request structs and typed results, and the errors are returned rather than logged for you. That means the caller decides whether a failed request is fatal, which the sample demonstrates by logging and exiting on the early setup calls and using the returned error from GetMe.

Where go-tdlib is the wrong tool

The clearest case against it is the Bot API. If everything you need is sending messages, receiving updates and managing chats through a bot token, the HTTP Bot API needs no native library, no cgo, no compiling TDLib from a pinned commit and no shared library shipped next to your binary. That comparison is not close on operational grounds, and the wrapper's own bot authorizer is the giveaway that the two approaches overlap.

The second limitation is the build and the deployment. A Go binary that links TDLib is not a static binary, so your container images need the shared library present, your cross compilation story gets complicated, and your CI needs a C++ toolchain and the right commit checked out. If your deployment is serverless functions with tight packaging limits, that is a genuine constraint rather than a preference.

Third, the project says plainly that it is work in progress and that the library API can be changed in the future. With no GitHub releases at all, there is no version history to diff against and no changelog to read before an upgrade. The generated types also change shape whenever the schema changes, so a TDLib bump can rewrite large parts of your call sites.

The realistic alternative is to write the HTTP calls to the Bot API directly, or to use an existing Go client library for the Bot API. Both avoid the native dependency entirely. The difference in approach is that go-tdlib gives you the full Telegram client protocol, including local database and file caching, secret chats, offline operation and updates, which the HTTP Bot API does not expose at all. If you need user accounts rather than bots, that is the whole argument for accepting the cgo build.

Editorial conclusion

Adopt go-tdlib if you are writing a Telegram client or bot backend in Go and want typed TDLib requests instead of hand written JSON calls, and you are willing to compile TDLib from source at a pinned commit. Do not adopt it if you only need to send messages through a bot token, since the Telegram Bot API needs no local library at all. Before you start, build TDLib from the commit the wrapper pins, check it with the version and commit_hash options the sample code reads, and read the notes, because the project states its API can still change.

Frequently asked questions

What is go-tdlib and which TDLib version does it support?

It is a Go wrapper for TDLib, the Telegram Database Library, with full support of TDLib. The supported version corresponds to the commit hash 49b3bcb, updated on 2026-05-08, and the installation notes ask you to check out that commit when building TDLib.

How do I build TDLib for go-tdlib?

Follow the TDLib build instructions with the option to install the built library to /usr/local rather than into the td/tdlib directory, and check out the supported commit. If you install it elsewhere, set the include and library paths with CGO_CFLAGS and CGO_LDFLAGS, linking against tdjson and adding an rpath so the shared library is found at run time.

What authorizers does go-tdlib provide?

There are three: a client authorizer for phone number and code login, a bot authorizer that takes a bot token, and a QR authorizer that receives a login link through a callback so a headless server can render it as an image and complete login without a keyboard.

How does go-tdlib receive updates from Telegram?

You pass a result handler when creating the client, with the NewCallbackResultHandler helper wrapping your callback. The callback receives a typed client.Type value, and its GetType method gives you the discriminant to switch on, so updates arrive as typed structures rather than parsed JSON.

Can go-tdlib use a proxy?

Yes. Proxy configuration is a client option built from an AddProxyRequest with a server, a port, an enable flag and a typed proxy type such as ProxyTypeSocks5 carrying a username and password, and it is passed to NewClient alongside other options.

Is go-tdlib maintained and how are its bindings generated?

The repository is not archived and the last push was on 2026-05-09, but there are no GitHub releases. The bindings are generated by the Makefile, which fetches the TL schema for the pinned commit, converts it to JSON and then writes the Go functions, types, unmarshaler and version file into the client package. The project also notes its API can still change.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. zelenin/go-tdlib on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/zelenin-go-tdlib.svg)](https://hysenlabs.com/projects/zelenin-go-tdlib)

Was this useful?