Reactionmenu
A library to create a discord.py 2.0+ paginator (reaction menu/buttons menu). Supports pagination with buttons, reactions, and category selection using selects.
Install / Use
/learn @Defxult/ReactionmenuREADME

How to install
You can install the latest PyPI version of the library by doing:
$ pip install reactionmenu
Or the development version:
$ pip install git+https://github.com/Defxult/reactionmenu
Intents
Minimum intents needed
bot = commands.Bot(..., intents=discord.Intents(messages=True, guilds=True, reactions=True, members=True))
ReactionMenu
class reactionmenu.ReactionMenu(method: Union[Context, discord.Interaction], /, *, menu_type: MenuType, **kwargs)
A ReactionMenu is a menu that uses emojis which are either custom guild emojis or a normal emoji to control the pagination process. If you're not looking for any of the fancy features and just want something simple, this is the one to use.

How to import
from reactionmenu import ReactionMenu, ReactionButton
This library comes with several methods and options in order to make a discord reaction menu simple. Once you have imported the proper classes, you will initialize the constructor like so:
menu = ReactionMenu(method, menu_type=ReactionMenu.TypeEmbed)
Parameters of the ReactionMenu constructor
method(Union[discord.ext.commands.Context, discord.Interaction]) A context or interaction objectmenu_type(MenuType) The configuration of the menuReactionMenu.TypeEmbed, a normal embed pagination menuReactionMenu.TypeEmbedDynamic, an embed pagination menu with dynamic dataReactionMenu.TypeText, a text only pagination menu
Kwargs of the ReactionMenu constructor
| Name | Type | Default Value | Used for | Info
-------|------|---------------|----------|------
| wrap_in_codeblock | str | None | ReactionMenu.TypeEmbedDynamic | The discord codeblock language identifier to wrap your data in. Example: ReactionMenu(ctx, ..., wrap_in_codeblock='py')
| custom_embed | discord.Embed | None | ReactionMenu.TypeEmbedDynamic | Embed object to use when adding data with ReactionMenu.add_row(). Used for styling purposes
| delete_on_timeout | bool | False | All menu types | Delete the menu when it times out
| clear_reactions_after | bool | True | All menu types | delete all reactions after the menu times out
| navigation_speed | str | ReactionMenu.NORMAL | All menu types | Sets if the user needs to wait for the reaction to be removed by the bot before "turning" the page. Setting the speed to ReactionMenu.FAST makes it so that there is no need to wait (reactions are not removed on each press) and can navigate lengthy menu's more quickly
| only_roles | List[discord.Role] | None | All menu types | If set, only members with any of the given roles are allowed to control the menu. The menu owner can always control the menu
| timeout | Union[int, float, None] | 60.0 | All menu types | The timer for when the menu times out. Can be None for no timeout
| show_page_director | bool | True | All menu types | Shown at the bottom of each embed page. "Page 1/20"
| name | str | None | All menu types | A name you can set for the menu
| style | str | "Page $/&" | All menu types | A custom page director style you can select. "$" represents the current page, "&" represents the total amount of pages. Example: ReactionMenu(ctx, ..., style='On $ out of &')
| all_can_click | bool | False | All menu types | Sets if everyone is allowed to control when pages are 'turned' when buttons are clicked
| delete_interactions | bool | True | All menu types | Delete the prompt message by the bot and response message by the user when asked what page they would like to go to when using ReactionButton.Type.GO_TO_PAGE
| rows_requested | int | None | ReactionMenu.TypeEmbedDynamic | The amount of information per ReactionMenu.add_row() you would like applied to each embed page
| remove_extra_emojis | bool | False | All menu types | If True, all emojis (reactions) added to the menu message that were not originally added to the menu will be removed
Pages for ReactionMenu
Depending on the menu_type, pages can either be a str, discord.Embed, or a combination of content and files (example below)
- If the
menu_typeisReactionMenu.TypeEmbed, use embeds - If the
menu_typeisReactionMenu.TypeText(text only menu) orReactionMenu.TypeEmbedDynamic(embed only menu), use strings. - Associated methods
ReactionMenu.add_page(embed: discord.Embed=MISSING, content: Optional[str]=None, files: Optional[Sequence[discord.File]]=None)ReactionMenu.add_pages(pages: Sequence[Union[discord.Embed, str]])ReactionMenu.add_row(data: str)ReactionMenu.remove_all_pages()ReactionMenu.clear_all_row_data()ReactionMenu.remove_page(page_number: int)ReactionMenu.set_main_pages(*embeds: Embed)ReactionMenu.set_last_pages(*embeds: Embed)
Adding Pages
# ReactionMenu.TypeEmbed
menu = ReactionMenu(method, menu_type=ReactionMenu.TypeEmbed)
menu.add_page(summer_embed)
menu.add_page(winter_embed)
# ReactionMenu.TypeText
menu = ReactionMenu(method, menu_type=ReactionMenu.TypeText)
menu.add_page(content='Its so hot!')
menu.add_page(content='Its so cold!')
ReactionMenu.TypeText
A TypeText menu is a text based pagination menu. No embeds are involved in the pagination process, only plain text is used.

Stacked Pages
With v3.1.0+, you can paginate with more than just an embed or text. You can combine text, embeds, as well as files. But depending on the menu_type the combination can be restricted. Here is an example of a menu with a menu_type of TypeEmbed that is stacked.
# You can use regular commands as well
@bot.tree.command(description="These are stacked pages", guild=discord.Object(id=...))
async def stacked(interaction: discord.Interaction):
menu = ReactionMenu(interaction, menu_type=ReactionMenu.TypeEmbed)
menu.add_page(discord.Embed(title="My Embed"), content="This content is stacked on top of a file", files=[discord.File("stacked.py")])
menu.add_page(discord.Embed(title="Hey Wumpos, can you say hi to the person reading this? 😃"))
menu.add_page(discord.Embed(title="Hi, I'm Wumpos!"), files=[discord.File("wumpos.gif")])
menu.add_button(ReactionButton.back())
menu.add_button(ReactionButton.next())
await menu.start()

Since the menu_type is TypeEmbed, there always has to be an embed on each page. If the menu_type was TypeText, embeds aren't allowed and you will be restricted to only using the files parameter.
ReactionMenu.TypeEmbedDynamic
A dynamic menu is used when you do not know how much information will be applied to the menu. For example, if you were to request information from a database, that information can always change. You query something and you might get 1,500 results back, and the next maybe only 800. A dynamic menu pieces all this information together for you and adds it to an embed page by rows of data. ReactionMenu.add_row() is best used in some sort of Iterable where everything can be looped through, but only add the amount of data you want to the menu page.
NOTE: In a dynamic menu, all added data is placed in the description section of an embed. If you choose to use a
custom_embed, all text in the description will be overridden with the data you add
- Associated methods
ReactionMenu.add_row(data: str)ReactionMenu.clear_all_row_data()ReactionMenu.set_main_pages(*embeds: Embed)ReactionMenu.set_last_pages(*embeds: Embed)
- The kwargs specifically made for a dynamic menu are:
rows_requested- The amount of rows you would like on each embed page before making a new pageReactionMenu(..., rows_requested=5)
custom_embed- An embed you have created to use as the embed pages. Used for your menu aestheticReactionMenu(..., custom_embed=red_embed)
wrap_in_codeblock- The language identifier when wrapping your data in a discord codeblock.ReactionMenu(..., wrap_in_codeblock='py')
Adding Rows/data
menu = ReactionMenu(ctx, menu_type=ReactionMenu.TypeEmbedDynamic, rows_requested=5)
for data in database.request('SELECT * FROM customers'):
menu.add_row(data)
Deleting Data
You can remove all the data you've added to a menu by using menu.clear_all_row_data()
Main/Last Pages
When using a dynamic menu, the only embed pages you see are from the data you've added. But if you would like to show more pages other than just the data, you can use methods ReactionMenu.set_main_pages() and ReactionMenu.set_last_pages(). Setting the main page(s), the embeds you set will be the first embeds that are shown when the menu starts. Setting the last page(s) are the last embeds shown
menu.set_main_pages(welcome_embed, announcement_embed)
for data in get_information():
menu.add_row(data)
menu.set_last_pages(additional_info_embed)
# NOTE: setting main/last pages can be set in any order
ReactionButto
Related Skills
imsg
351.4kiMessage/SMS CLI for listing chats, history, and sending messages via Messages.app.
claude-opus-4-5-migration
110.7kMigrate prompts and code from Claude Sonnet 4.0, Sonnet 4.5, or Opus 4.1 to Opus 4.5
discord
351.4kDiscord ops via the message tool (channel=discord).
session-logs
351.4kSearch and analyze your own session logs (older/parent conversations) using jq.
