Docs / Developers / Build / Buttons & menus
BuildSDK 0.10.1

Buttons & menus

These docs are public. To publish a plugin you need a YourBot account: sign in to open the Dev Portal.

Component classes build Discord component JSON for you. They take string-style values, not enums.

Button

from yourbot_sdk import Button, ActionRow

Button(
    label="Click me",
    custom_id="btn_hi",
    style="primary",   # "primary" | "secondary" | "success" | "danger" | "link"
    emoji="🎉",
    disabled=False,
)

# Link buttons use url instead of custom_id
Button(label="Docs", url="https://yourbot.gg/dev/docs", style="link")

SelectMenu

from yourbot_sdk import SelectMenu, SelectOption, ActionRow

menu = SelectMenu(
    custom_id="pick_role",
    options=[
        SelectOption(label="Gamer", value="role_gamer", description="Get pinged for game nights"),
        SelectOption(label="Music",  value="role_music", default=True),  # pre-selected
        SelectOption(label="Art",    value="role_art", emoji="🎨"),
    ],
    placeholder="Pick your interests…",
    min_values=1,
    max_values=2,
    disabled=False,
)

ActionRow

Discord shows components in rows. Wrap one or more components in an ActionRow and pass it as components=[…]. A row holds at most 5 components (extras are silently dropped), and over-long strings on any builder (labels, placeholders, custom_ids) are silently truncated to Discord's caps rather than raising — keep them short by construction:

@plugin.on_slash_command("menu")
def menu_cmd(ctx: Context, event: dict):
    row = ActionRow(
        Button("Yes", custom_id="yes", style="success"),
        Button("No",  custom_id="no",  style="danger"),
    )
    ctx.interaction.respond(content="Pick one:", components=[row])

@plugin.on_component("yes")
def yes_clicked(ctx: Context, event: dict):
    ctx.interaction.respond(content="✅", ephemeral=True)

@plugin.on_component("no")
def no_clicked(ctx: Context, event: dict):
    ctx.interaction.respond(content="❌", ephemeral=True)

Dynamic custom_ids: prefix matching

@plugin.on_component("exact_id") matches the whole string. When your custom_id encodes state (page numbers, vote targets), register with prefix= instead and parse the rest yourself. Exactly one of custom_id= or prefix= must be given.

@plugin.on_component(prefix="page:")
def handle_page(ctx: Context, event: dict):
    cid = event["custom_id"]            # e.g. "page:next:5"
    _, direction, index = cid.split(":", 2)
    ...

Buttons posted by an older version of your plugin stay clickable on Discord forever. If you encode state in custom_id, embed a schema version too (e.g. "v2:vote:42") so stale buttons can be detected and politely refused.

Updating a message in place v0.7.0

Inside a component handler, pass update_message=True to respond() to edit the message the button or menu is attached to (game boards, pagination, live leaderboards) instead of sending a new reply:

@plugin.on_component(prefix="counter:")
def bump(ctx: Context, event: dict):
    count = int(event["custom_id"].split(":")[1]) + 1
    ctx.interaction.respond(
        content=f"Count: **{count}**",
        components=[ActionRow(Button("+1", custom_id=f"counter:{count}"))],
        update_message=True,
    )
  • Component interactions only. The platform rejects it for slash commands and modal submits.
  • The fields you pass replace the message's current content/embeds/components, so pass everything the updated message should contain.
  • ephemeral is ignored (the message keeps its original visibility). You can call it repeatedly within the 15-minute interaction window.
YourBot docs Reference tables are generated from the code that is running. Ask in Discord Suggest a correction