Buttons
Overview
components.Button has six current styles in this repository: primary, secondary, success, danger, link, and premium. Application-owned buttons use a CustomID and are routed by bot.Router; link buttons use a URL and navigate without sending a component interaction.
Tutorial: Interactive And Link Buttons
- Build a custom-ID button with
SetCustomID,SetLabel, andSetStyle. - Put it in an action row.
- Register the exact ID with
router.Button. - Acknowledge a click with
UpdateContent,Update, orDeferUpdate. - Build link buttons with
SetURLandButtonStyleLink; do not register a route for them.
Complete Runnable Example
Copy to examples/buttons/main.go, set DISCORD_TOKEN, and run it. Invoke /buttons, then try both controls.
package main
import (
"log"
"os"
"github.com/discord-go/discord.go/bot"
"github.com/discord-go/discord.go/components"
"github.com/discord-go/discord.go/intents"
"github.com/discord-go/discord.go/interactions"
)
func main() {
token := os.Getenv("DISCORD_TOKEN")
if token == "" {
log.Fatal("DISCORD_TOKEN is required")
}
router := bot.NewRouter()
router.Command("buttons", "Show button types", func(ctx *bot.InteractionContext) {
custom := components.NewButtonBuilder().
SetCustomID("buttons:done").
SetLabel("Complete").
SetStyle(components.ButtonStyleSuccess).
Build()
link := components.NewButtonBuilder().
SetLabel("Discord developers").
SetURL("https://discord.com/developers/docs").
SetStyle(components.ButtonStyleLink).
Build()
row := components.NewActionRowBuilder().AddComponents(custom, link).Build()
if err := ctx.ReplyComplex(&interactions.InteractionCallbackData{
Content: "The first button is routed by the bot. The second opens a URL.",
Components: []components.Component{row},
}); err != nil {
log.Printf("button response: %v", err)
}
})
router.Button("buttons:done", func(ctx *bot.InteractionContext) {
if err := ctx.UpdateContent("Completed. The link button never reached the bot."); err != nil {
log.Printf("button update: %v", err)
}
})
b := bot.New(token, bot.WithIntents(intents.Guilds), bot.WithRouter(router))
if err := b.Run(); err != nil {
log.Fatal(err)
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Explanation
The string passed to SetCustomID must equal the string passed to router.Button. A custom ID is visible to clients and may be replayed, so it is a routing hint rather than a security boundary. If a button belongs to a specific user or resource, check that relationship inside the handler.
For IDs such as ticket:close:123, use router.ButtonPrefix("ticket:close:", handler), then read the suffix with ctx.Suffix() (which returns "123"), and verify that the actor is allowed to close the ticket before performing the operation.
Slow Handlers
The component response deadline is short. Defer before slow work:
router.Button("refresh", func(ctx *bot.InteractionContext) {
if err := ctx.DeferUpdate(); err != nil {
return
}
// Perform bounded work, then edit the original interaction response.
if _, err := ctx.EditReply("Refreshed"); err != nil {
log.Printf("edit button response: %v", err)
}
})2
3
4
5
6
7
8
9
UpdateContent is the simplest immediate acknowledgement. Update is useful when the new content, embeds, or component state must be sent together.
Common Mistakes
- Calling
Replyfrom a button handler when the source message should change. - Sleeping or doing REST work before
DeferUpdate. - Assigning both
CustomIDandURL; link buttons should be navigation only. - Assuming disabled buttons enforce authorization.
- Forgetting to return/log an error from the acknowledgement.
Expected Result
/buttons renders one application button and one link button. Clicking Complete updates the original message. Clicking the link opens the Discord developer documentation and does not invoke a router route.