Skip to main content
Telebot’s editing API is built around the Editable interface. Any struct that returns a message ID and chat ID can be edited — including messages you retrieve from your database.

The Editable interface

type Editable interface {
    // MessageSig returns the message ID and chat ID.
    // For inline messages, return chatID = 0.
    MessageSig() (messageID string, chatID int64)
}
Telebot uses these two values to construct editMessageText, editMessageMedia, and similar API calls. Any struct that provides them can be passed to b.Edit(), b.Delete(), b.Pin(), and b.Unpin().

StoredMessage

StoredMessage is a ready-to-use implementation suitable for storing in a database:
type StoredMessage struct {
    MessageID string `sql:"message_id" json:"message_id"`
    ChatID    int64  `sql:"chat_id"    json:"chat_id"`
}

func (x StoredMessage) MessageSig() (string, int64) {
    return x.MessageID, x.ChatID
}
You can embed it into a larger model:
type Post struct {
    tele.StoredMessage
    Title   string
    Content string
    SentAt  time.Time
}

Editing messages

b.Edit — text, media, or markup

b.Edit() dispatches to the correct Telegram method based on the type of what:
// Edit text
b.Edit(msg, "Updated text")

// Edit text with HTML
b.Edit(msg, "<b>Updated</b> text", tele.ModeHTML)

// Replace the inline keyboard
b.Edit(msg, newMarkup)

// Replace a photo
b.Edit(msg, &tele.Photo{File: tele.FromDisk("new.jpg")})

// Edit an inline message (from a callback)
b.Edit(c.Callback(), "Updated inline message")

b.EditCaption — media captions

_, err := b.EditCaption(msg, "New caption for this image")

// With parse mode
_, err = b.EditCaption(msg, "<b>New</b> caption", tele.ModeHTML)

b.EditReplyMarkup — inline keyboard only

var newMenu = &tele.ReplyMarkup{}
newMenu.Inline(newMenu.Row(
    newMenu.Data("Refresh", "refresh"),
))

_, err := b.EditReplyMarkup(msg, newMenu)

// Pass nil to remove the keyboard entirely
_, err = b.EditReplyMarkup(msg, nil)

Deleting messages

err := b.Delete(msg)
A message can only be deleted if it was sent less than 48 hours ago. For dice messages in private chats the window is reversed: they can only be deleted more than 24 hours after sending. The bot must have permission to delete messages in the chat.
Delete multiple messages at once:
err := b.DeleteMany([]tele.Editable{msg1, msg2, msg3})

Pinning and unpinning

// Pin (supports tele.Silent to pin without notification)
err := b.Pin(msg)
err = b.Pin(msg, tele.Silent)

// Unpin a specific message
err = b.Unpin(chat, msgID)

// Unpin all pinned messages
err = b.UnpinAll(chat)

Pattern: store, retrieve, edit

A typical workflow in a bot that needs to update messages later:
// 1. Send and store
b.Handle("/post", func(c tele.Context) error {
    msg, err := b.Send(c.Chat(), "Initial content")
    if err != nil {
        return err
    }

    stored := tele.StoredMessage{
        MessageID: strconv.Itoa(msg.ID),
        ChatID:    msg.Chat.ID,
    }
    db.Save(&stored) // persist to your DB
    return nil
})

// 2. Retrieve and edit later (e.g. from a scheduled job)
stored := db.GetStoredMessage(id)
_, err := b.Edit(stored, "Updated content at " + time.Now().Format(time.Kitchen))

Editing via Context

When inside a callback handler, use the context shortcuts — they automatically resolve the correct message:
b.Handle(&btnEdit, func(c tele.Context) error {
    // Edits the message the button is attached to
    return c.Edit("Edited via context!")
})

b.Handle(&btnCaption, func(c tele.Context) error {
    return c.EditCaption("New caption")
})

// EditOrSend: edit if callback, send if not
b.Handle("/update", func(c tele.Context) error {
    return c.EditOrSend("Hello from /update")
})

Custom Editable

Implement Editable on any struct:
type MyMessage struct {
    ID     string
    ChatID int64
    // ... other fields
}

func (m *MyMessage) MessageSig() (string, int64) {
    return m.ID, m.ChatID
}

// Now usable everywhere Editable is accepted
b.Edit(&myMsg, "Updated!")
b.Delete(&myMsg)

Build docs developers (and LLMs) love