Telebot is a Telegram bot framework in Go.
Switch branches/tags
Nothing to show
Clone or download
Latest commit b59ea4a Jul 19, 2018
Failed to load latest commit information.
.gitignore Initial commit Jun 25, 2015
.travis.yml Merging #20 into tucnak:master from aladine:patch-3 Oct 16, 2015
LICENSE Initial commit Jun 25, 2015 Set a unique string ID for each result in inline query Answer Mar 20, 2018
admin.go Raw() method introduced. KeyboardButton -> ReplyButton. Nov 28, 2017
api.go add option to pass custom http.Client to tb.NewBot() in case of proxy… Apr 18, 2018
bot.go Merge pull request #146 from aprosvetova/patch-1 Jul 18, 2018
callbacks.go JSON fix for buttons. Dec 11, 2017
chat.go Administration tools, this resolves #43. Nov 21, 2017
editable.go Possibly breaking some builds, but inline message IDs won't work Dec 26, 2017
file.go Albums added, this commit resolves #103. Nov 25, 2017
filters.go Filters. Dec 17, 2017
inline.go Possibly breaking some builds, but inline message IDs won't work Dec 26, 2017
inline_types.go Possibly breaking some builds, but inline message IDs won't work Dec 26, 2017
input_types.go Completing inline API, closing #104. Nov 26, 2017
media.go Completing regular API now. Nov 26, 2017
message.go Possibly breaking some builds, but inline message IDs won't work Dec 26, 2017
options.go Add ReplyKeyboardRemove Dec 30, 2017
poller.go Minor API changes. Dec 11, 2017
sendable.go Handle Video with no sound (fix #133) Jul 18, 2018
stickers.go Cleanup, documentation, bikeshedding. Nov 19, 2017
telebot.go Fixed could not send message to private channel Dec 30, 2017
telebot_test.go add option to pass custom http.Client to tb.NewBot() in case of proxy… Apr 18, 2018
util.go fix lint errors in current code Jun 30, 2018
webhook.go allow custom http muxes too Jul 1, 2018


"I never knew creating Telegram bots could be so sexy!"

GoDoc Travis

go get -u


Telebot is a bot framework for Telegram Bot API. This package provides the best of its kind API for command routing, inline query requests and keyboards, as well as callbacks. Actually, I went a couple steps further, so instead of making a 1:1 API wrapper I chose to focus on the beauty of API and performance. Some of the strong sides of telebot are:

  • Real concise API
  • Command routing
  • Middleware
  • Transparent File API
  • Effortless bot callbacks

All the methods of telebot API are extremely easy to memorize and get used to. Also, consider Telebot a highload-ready solution. I'll test and benchmark the most popular actions and if necessary, optimize against them without sacrificing API quality.

Getting Started

Let's take a look at the minimal telebot setup:

package main

import (

	tb ""

func main() {
	b, err := tb.NewBot(tb.Settings{
		Token:  "TOKEN_HERE",
		Poller: &tb.LongPoller{Timeout: 10 * time.Second},

	if err != nil {

	b.Handle("/hello", func(m *tb.Message) {
		b.Send(m.Sender, "hello world")


Simple, innit? Telebot's routing system takes care of deliviering updates to their endpoints, so in order to get to handle any meaningful event, all you got to do is just plug your function to one of the Telebot-provided endpoints. You can find the full list here.

b, _ := tb.NewBot(settings)

b.Handle(tb.OnText, func(m *tb.Message) {
	// all the text messages that weren't
	// captured by existing handlers

b.Handle(tb.OnPhoto, func(m *tb.Message) {
	// photos only

b.Handle(tb.OnChannelPost, func (m *tb.Message) {
	// channel posts only

b.Handle(tb.Query, func (q *tb.Query) {
	// incoming inline queries

Now there's a dozen of supported endpoints (see package consts). Let me know if you'd like to see some endpoint or endpoint idea implemented. This system is completely extensible, so I can introduce them without breaking backwards-compatibity.


Telebot doesn't really care how you provide it with incoming updates, as long as you set it up with a Poller:

// Poller is a provider of Updates.
// All pollers must implement Poll(), which accepts bot
// pointer and subscription channel and start polling
// synchronously straight away.
type Poller interface {
	// Poll is supposed to take the bot object
	// subscription channel and start polling
	// for Updates immediately.
	// Poller must listen for stop constantly and close
	// it as soon as it's done polling.
	Poll(b *Bot, updates chan Update, stop chan struct{})

Telegram Bot API supports long polling and webhook integration. I don't really care about webhooks, so the only concrete Poller you'll find in the library is the LongPoller. Poller means you can plug telebot into whatever existing bot infrastructure (load balancers?) you need, if you need to. Another great thing about pollers is that you can chain them, making some sort of middleware:

poller := &tb.LongPoller{Timeout: 15 * time.Second}
spamProtected := tb.NewMiddlewarePoller(poller, func(upd *tb.Update) bool {
	if upd.Message == nil {
		return true

	if strings.Contains(upd.Message.Text, "spam") {
		return false

	return true

bot, _ := tb.NewBot(tb.Settings{
	// ...
	Poller: spamProtected,

// graceful shutdown
go func() {
	<-time.After(N * time.Second)

bot.Start() // blocks until shutdown

fmt.Println(poller.LastUpdateID) // 134237


When handling commands, Telebot supports both direct (/command) and group-like syntax (/command@botname) and will never deliver messages addressed to some other bot, even if privacy mode is off. For simplified deep-linking, telebot also extracts payload:

// Command: /start <PAYLOAD>
b.Handle("/start", func(m *tb.Message) {
	if !m.Private() {

	fmt.Println(m.Payload) // <PAYLOAD>


Telegram allows files up to 20 MB in size.

Telebot allows to both upload (from disk / by URL) and download (from Telegram) and files in bot's scope. Also, sending any kind of media with a File created from disk will upload the file to Telegram automatically:

a := &tb.Audio{File: tb.FromDisk("file.ogg")}

fmt.Println(a.OnDisk()) // true
fmt.Println(a.InCloud()) // false

// Will upload the file from disk and send it to recipient
bot.Send(recipient, a)

// Next time you'll be sending this very *Audio, Telebot won't
// re-upload the same file but rather utilize its Telegram FileID
bot.Send(otherRecipient, a)

fmt.Println(a.OnDisk()) // true
fmt.Println(a.InCloud()) // true
fmt.Println(a.FileID) // <telegram file id: ABC-DEF1234ghIkl-zyx57W2v1u123ew11>

You might want to save certain Files in order to avoid re-uploading. Feel free to marshal them into whatever format, File only contain public fields, so no data will ever be lost.


Send is undoubteldy the most important method in Telebot. Send() accepts a Recipient (could be user, group or a channel) and a Sendable. FYI, not only all telebot-provided media types (Photo, Audio, Video, etc.) are Sendable, but you can create composite types of your own. As long as they satisfy Sendable, Telebot will be able to send them out.

// Sendable is any object that can send itself.
// This is pretty cool, since it lets bots implement
// custom Sendables for complex kind of media or
// chat objects spanning across multiple messages.
type Sendable interface {
    Send(*Bot, Recipient, *SendOptions) (*Message, error)

The only type at the time that doesn't fit Send() is Album and there is a reason for that. Albums were added not so long ago, so they are slightly quirky for backwards compatibilities sake. In fact, an Album can be sent, but never received. Instead, Telegram returns a []Message, one for each media object in the album:

p := &tb.Photo{File: tb.FromDisk("chicken.jpg")}
v := &tb.Video{File: tb.FromURL("http://video.mp4")}

msgs, err := b.SendAlbum(user, tb.Album{p, v})

Send options

Send options are objects and flags you can pass to Send(), Edit() and friends as optional arguments (following the recipient and the text/media). The most important one is called SendOptions, it lets you control all the properties of the message supported by Telegram. The only drawback is that it's rather inconvenient to use at times, so Send() supports multiple shorthands:

// regular send options
b.Send(user, "text", &tb.SendOptions{
	// ...

// ReplyMarkup is a part of SendOptions,
// but often it's the only option you need
b.Send(user, "text", &tb.ReplyMarkup{
	// ...

// flags: no notification && no web link preview
b.Send(user, "text", tb.Silent, tb.NoPreview)

Full list of supported option-flags you can find here.


If you want to edit some existing message, you don't really need to store the original *Message object. In fact, upon edit, Telegram only requires chat_id and message_id. So you don't really need the Message as the whole. Also you might want to store references to certain messages in the database, so I thought it made sense for any Go struct to be editable as a Telegram message, to implement Editable:

// Editable is an interface for all objects that
// provide "message signature", a pair of 32-bit
// message ID and 64-bit chat ID, both required
// for edit operations.
// Use case: DB model struct for messages to-be
// edited with, say two collums: msg_id,chat_id
// could easily implement MessageSig() making
// instances of stored messages editable.
type Editable interface {
	// MessageSig is a "message signature".
	// For inline messages, return chatID = 0.
	MessageSig() (messageID int, chatID int64)

For example, Message type is Editable. Here is the implementation of StoredMessage type, provided by telebot:

// StoredMessage is an example struct suitable for being
// stored in the database as-is or being embedded into
// a larger struct, which is often the case (you might
// want to store some metadata alongside, or might not.)
type StoredMessage struct {
	MessageID int   `sql:"message_id" json:"message_id"`
	ChatID    int64 `sql:"chat_id" json:"chat_id"`

func (x StoredMessage) MessageSig() (int, int64) {
	return x.MessageID, x.ChatID

Why bother at all? Well, it allows you to do things like this:

// just two integer columns in the database
var msgs []tb.StoredMessage
db.Find(&msgs) // gorm syntax

for _, msg := range msgs {
	bot.Edit(&msg, "Updated text.")
	// or

I find it incredibly neat. Worth noting, at this point of time there exists another method in the Edit family, EditCaption() which is of a pretty rare use, so I didn't bother including it to Edit(), just like I did with SendAlbum() as it would inevitably lead to unnecessary complications.

var m *Message

// change caption of a photo, audio, etc.
bot.EditCaption(m, "new caption")


Telebot supports both kinds of keyboards Telegram provides: reply and inline keyboards. Any button can also act as an endpoints for Handle():

func main() {
	b, _ := tb.NewBot(tb.Settings{...})

	// This button will be displayed in user's
	// reply keyboard.
	replyBtn := tb.ReplyButton{Text: "🌕 Button #1"}
	replyKeys := [][]tb.ReplyButton{
		// ...

	// And this one — just under the message itself.
	// Pressing it will cause the client to send
	// the bot a callback.
	// Make sure Unique stays unique as it has to be
	// for callback routing to work.
	inlineBtn := tb.InlineButton{
		Unique: "sad_moon",
		Text: "🌚 Button #2",
	inlineKeys := [][]tb.InlineButton{
		// ...

	b.Handle(&replyBtn, func(m *tb.Message) {
		// on reply button pressed

	b.Handle(&inlineBtn, func(c *tb.Callback) {
		// on inline button pressed (callback!)

		// always respond!
		b.Respond(c, &tb.CallbackResponse{...})

	// Command: /start <PAYLOAD>
	b.Handle("/start", func(m *tb.Message) {
		if !m.Private() {

		b.Send(m.Sender, "Hello!", &tb.ReplyMarkup{
			ReplyKeyboard:  replyKeys,
			InlineKeyboard: inlineKeys,


Inline mode

So if you want to handle incoming inline queries you better plug the tb.OnQuery endpoint and then use the Answer() method to send a list of inline queries back. I think at the time of writing, telebot supports all of the provided result types (but not the cached ones). This is how it looks like:

b.Handle(tb.OnQuery, func(q *tb.Query) {
	urls := []string{

	results := make(tb.Results, len(urls)) // []tb.Result
	for i, url := range urls {
		result := &tb.PhotoResult{
			URL: url,

			// required for photos
			ThumbURL: url,

		results[i] = result
		results[i].SetResultID(strconv.Itoa(i)) // It's needed to set a unique string ID for each result

	err := b.Answer(q, &tb.QueryResponse{
		Results: results,
		CacheTime: 60, // a minute

	if err != nil {

There's not much to talk about really. It also support some form of authentication through deep-linking. For that, use fields SwitchPMText and SwitchPMParameter of QueryResponse.


  1. Fork it
  2. Clone it: git clone
  3. Create your feature branch: git checkout -b my-new-feature
  4. Make changes and add them: git add .
  5. Commit: git commit -m 'Add some feature'
  6. Push: git push origin my-new-feature
  7. Pull request


I do coding for fun but I also try to search for interesting solutions and optimize them as much as possible. If you feel like it's a good piece of software, I wouldn't mind a tip!

Bitcoin: 1DkfrFvSRqgBnBuxv9BzAz83dqur5zrdTH


Telebot is distributed under MIT.