API reference
Views
Buttons and menus whose clicks are handled inside the same command. The best choice for games, confirmations and menus.
A View is a set of buttons and menus. When someone clicks, a function in the same command runs. That function can see every variable of the command.
Use a View when the clicks happen while the command is running: a "Yes / No" question, a game, a page of choices. Use a component handler only for buttons that must keep working for days, like a role panel.
Read Buttons and menus to choose between the two.
A full example#
# pre-hook
class Confirm(View):
def __init__(self, user_id):
self.user_id = user_id # only this member can click
self.choice = None
@button(label="Yes", style="success", custom_id="yes")
async def yes(self, interaction, component):
await defer(interaction, action="update")
self.choice = "yes"
self.disable_all_components()
await self.update_view()
self.stop()
@button(label="No", style="danger", custom_id="no")
async def no(self, interaction, component):
await defer(interaction, action="update")
self.choice = "no"
self.disable_all_components()
await self.update_view()
self.stop()
view = Confirm(user_id=user.id)
view.interaction = interaction # lets update_view() change the reply
set_view(view)
set_content("Delete all your saved notes?")# post-hook
if view.choice == "yes":
send_message(channel, "Deleted.")
elif view.choice is None:
send_message(channel, "No answer. Nothing was deleted.")What happens:
- The pre-hook makes the View and calls
set_view. - The bot sends the reply with the buttons and waits for clicks.
- A click runs
yesorno.self.stop()ends the View. - The post-hook runs.
view.choiceholds the answer.
Rules for Views
- Never call
super().__init__()in your View. - After
set_view(view), do not callview.wait(). The bot does it. - Callbacks always look like
async def name(self, interaction, component).
View attributes#
Set these on your View.
| Attribute | What it does |
|---|---|
user_id | A member ID, or a list of IDs, allowed to click. None lets anyone click. |
interaction | Set to interaction so update_view() changes the command's reply. |
message | Set when the View is on a message you sent. send_message sets it for you. |
guild_id | Set for you by set_view and send_message. |
View methods#
self.stop#
Ends the View. wait() then returns "stopped".
self.stop()stop() also ends the callback. Lines after it do not run, so call it last.
self.update_view#
Shows your changes to the buttons on the message. Needs await.
await self.update_view()self.update_component#
Changes one button or menu. Use the custom_id you wrote in the decorator.
self.update_component(custom_id, **changes)self.count += 1
self.update_component("add", label=f"Clicked {self.count} times")
await self.update_view()self.disable_all_components#
Greys out every button and menu. Pass ignore=[...] to keep some active.
self.disable_all_components(ignore=None)view.wait#
Waits for clicks until the View stops or nobody clicks for timeout seconds. Needs await.
await view.wait(timeout=180, disable_on_timeout=False, ignore_errors=False)| Argument | What it does |
|---|---|
timeout | Seconds with no clicks before the View ends. Each click starts the clock again. |
disable_on_timeout | Grey out the buttons when time runs out. |
ignore_errors | Keep the View going when a callback has an error. |
Returns "stopped" or "timeout".
Only call wait yourself when you did not use set_view, for example when the View is on a message from send_message:
view = Poll()
send_message(channel, "Vote now!", components=view)
result = await view.wait(timeout=60, disable_on_timeout=True)button#
Adds a button. Put it above an async def inside your View.
@button(
label,
custom_id=None,
style="primary",
is_disabled=False,
emoji=...,
row=1,
url=None,
hidden=False,
)| Argument | What it does |
|---|---|
label | The button text. |
custom_id | A name for the button. Needed for update_component. |
style | primary (blue), secondary (grey), success (green), danger (red), link. |
emoji | An emoji shown on the button. |
row | Which row, from 1 to 5. |
url | Makes it a link button. Link buttons do not run a callback. |
hidden | True hides the button until you show it with update_component. |
A wrong style silently becomes primary.
In signatures on this page, ... means the argument is optional and has no default. Leave it out when you do not need it.
Extra keyword arguments are kept in component.metadata, so a callback can read them.
text_menu#
Adds a menu with your own options.
@text_menu(
options,
custom_id=None,
placeholder=...,
min_values=0,
max_values=1,
is_disabled=False,
row=None,
hidden=False,
)options is a list of TextOption:
TextOption(label, value, description=..., emoji=..., default=False)What the member picked is in interaction.values. It is always a list, even with max_values=1.
class Colors(View):
@text_menu(
options=[
TextOption(label="Red", value="red", emoji="🔴"),
TextOption(label="Blue", value="blue", emoji="🔵"),
],
custom_id="color",
placeholder="Pick a color",
)
async def pick(self, interaction, component):
await defer(interaction, action="update")
self.color = interaction.values[0]
self.stop()Watch out
Use interaction.values, not component.values. component is what you declared, not what the member picked.
role_menu#
Adds a menu that lists the server's roles. interaction.values holds the picked role IDs.
@role_menu(custom_id=None, placeholder=..., min_values=0, max_values=1,
is_disabled=False, row=None, hidden=False)user_menu#
Adds a menu that lists members. interaction.values holds the picked member IDs. Same arguments as role_menu.
@user_menu(custom_id=None, placeholder=..., min_values=0, max_values=1)channel_menu#
Adds a menu that lists channels. interaction.values holds the picked channel IDs.
@channel_menu(channel_types, custom_id=None, placeholder=...,
min_values=0, max_values=1)channel_types is a list from: text, voice, category, news, news-thread, public-thread, private-thread, forum, stage.
Layout rules#
Discord limits how buttons and menus fit on a message:
- At most 5 rows.
- At most 5 buttons in a row.
- A menu takes a whole row. It cannot share a row with buttons or another menu.
The bot stops with a clear message when a View breaks a rule.
Answering a click#
Discord wants an answer to every click within 3 seconds. Start each callback with one of these:
| Call | Use when |
|---|---|
await defer(interaction, action="update") | You change the message the buttons are on. |
await defer(interaction, ephemeral=True) | You send a new private message later. |
await send_modal(form, interaction) | You open a form. Opening a form is the answer. Do not defer too. |
See Interactions and Modals.