ext.pages extension provides a powerful pagination system for Discord bots, allowing you to create interactive, button-based navigation through multiple pages of content.
Installation
from discord.ext import pages
Basic Pagination
Simple String Pages
import discord
from discord.ext import commands, pages as paginator
bot = commands.Bot(command_prefix="!")
@bot.slash_command()
async def fruits(ctx):
"""Shows a list of fruits with pagination."""
page_list = [
"🍎 Apples",
"🍌 Bananas",
"🍊 Oranges",
"🍇 Grapes",
"🍓 Strawberries"
]
paginator_view = paginator.Paginator(pages=page_list)
await paginator_view.respond(ctx.interaction, ephemeral=False)
Embed Pages
@bot.slash_command()
async def userinfo(ctx, user: discord.Member):
"""Shows detailed user information across multiple pages."""
embed1 = discord.Embed(
title=f"{user.name}'s Profile",
description=f"ID: {user.id}",
color=discord.Color.blue()
)
embed1.set_thumbnail(url=user.display_avatar.url)
embed2 = discord.Embed(
title="Roles",
description="\n".join([role.mention for role in user.roles]),
color=discord.Color.green()
)
embed3 = discord.Embed(
title="Account Info",
description=f"Created: {user.created_at.strftime('%Y-%m-%d')}",
color=discord.Color.gold()
)
page_list = [embed1, embed2, embed3]
paginator_view = paginator.Paginator(pages=page_list)
await paginator_view.respond(ctx.interaction)
Page Class
ThePage class provides more control over individual pages.
from discord.ext.pages import Page
@bot.slash_command()
async def mixed_content(ctx):
"""Shows pages with mixed content types."""
page1 = Page(
content="Welcome to the bot!",
embeds=[
discord.Embed(title="Introduction", description="Let's get started")
]
)
page2 = Page(
content="Here are some features:",
embeds=[
discord.Embed(title="Feature 1", description="Description 1"),
discord.Embed(title="Feature 2", description="Description 2")
]
)
page3 = Page(
embeds=[discord.Embed(title="Thank you!", description="End of tour")]
)
paginator_view = paginator.Paginator(pages=[page1, page2, page3])
await paginator_view.respond(ctx.interaction)
Paginator Options
Basic Configuration
- Show/Hide Disabled
- Loop Pages
- Page Indicator
- Timeout
# Show disabled buttons (default)
paginator_view = paginator.Paginator(
pages=page_list,
show_disabled=True
)
# Hide disabled buttons
paginator_view = paginator.Paginator(
pages=page_list,
show_disabled=False
)
# Enable looping (wrap around at end)
paginator_view = paginator.Paginator(
pages=page_list,
loop_pages=True
)
# Hide page indicator
paginator_view = paginator.Paginator(
pages=page_list,
show_indicator=False
)
# Set custom timeout (in seconds)
paginator_view = paginator.Paginator(
pages=page_list,
timeout=300.0, # 5 minutes
disable_on_timeout=True
)
Author Check
# Only command author can use buttons
paginator_view = paginator.Paginator(
pages=page_list,
author_check=True # Default
)
# Anyone can use buttons
paginator_view = paginator.Paginator(
pages=page_list,
author_check=False
)
Custom Buttons
Custom Button Styles
from discord.ext.pages import PaginatorButton
@bot.slash_command()
async def custom_buttons(ctx):
"""Paginator with custom button styling."""
custom_buttons = [
PaginatorButton("first", label="<<", style=discord.ButtonStyle.blurple),
PaginatorButton("prev", label="<", style=discord.ButtonStyle.red),
PaginatorButton("page_indicator", style=discord.ButtonStyle.gray, disabled=True),
PaginatorButton("next", label=">", style=discord.ButtonStyle.green),
PaginatorButton("last", label=">>", style=discord.ButtonStyle.blurple),
]
paginator_view = paginator.Paginator(
pages=page_list,
use_default_buttons=False,
custom_buttons=custom_buttons
)
await paginator_view.respond(ctx.interaction)
Emoji Buttons
@bot.slash_command()
async def emoji_buttons(ctx):
"""Paginator with emoji buttons."""
emoji_buttons = [
PaginatorButton("first", emoji="⏪", style=discord.ButtonStyle.green),
PaginatorButton("prev", emoji="⬅", style=discord.ButtonStyle.green),
PaginatorButton("page_indicator", style=discord.ButtonStyle.gray, disabled=True),
PaginatorButton("next", emoji="➡", style=discord.ButtonStyle.green),
PaginatorButton("last", emoji="⏩", style=discord.ButtonStyle.green),
]
paginator_view = paginator.Paginator(
pages=page_list,
use_default_buttons=False,
custom_buttons=emoji_buttons
)
await paginator_view.respond(ctx.interaction)
Removing Buttons
@bot.slash_command()
async def minimal_buttons(ctx):
"""Paginator with only prev/next buttons."""
paginator_view = paginator.Paginator(pages=page_list)
paginator_view.remove_button("first")
paginator_view.remove_button("last")
await paginator_view.respond(ctx.interaction)
Page Groups
Page groups allow users to switch between different sets of pages.from discord.ext.pages import PageGroup
@bot.slash_command()
async def help_menu(ctx):
"""Shows a help menu with different categories."""
# General commands
general_pages = [
discord.Embed(title="General Commands", description="!ping - Check bot latency"),
discord.Embed(title="General Commands", description="!info - Bot information"),
]
# Moderation commands
mod_pages = [
discord.Embed(title="Moderation", description="!kick - Kick a member"),
discord.Embed(title="Moderation", description="!ban - Ban a member"),
]
# Create page groups
page_groups = [
PageGroup(
pages=general_pages,
label="General Commands",
description="Basic bot commands",
emoji="📋",
default=True # Show this group first
),
PageGroup(
pages=mod_pages,
label="Moderation",
description="Moderation commands",
emoji="🔨"
),
]
paginator_view = paginator.Paginator(
pages=page_groups,
show_menu=True # Show the dropdown menu
)
await paginator_view.respond(ctx.interaction)
Custom Views
Add custom buttons and select menus alongside pagination.@bot.slash_command()
async def custom_view(ctx):
"""Paginator with additional custom buttons."""
# Create custom view with extra buttons
view = discord.ui.View()
view.add_item(
discord.ui.Button(
label="Visit Website",
url="https://pycord.dev",
row=1
)
)
# Create paginator with custom view
paginator_view = paginator.Paginator(
pages=page_list,
custom_view=view
)
await paginator_view.respond(ctx.interaction)
Sending Methods
Respond to Interaction
@bot.slash_command()
async def show_pages(ctx):
paginator_view = paginator.Paginator(pages=page_list)
await paginator_view.respond(ctx.interaction, ephemeral=False)
Send to Context
@bot.command()
async def show_pages(ctx):
"""Prefix command version."""
paginator_view = paginator.Paginator(pages=page_list)
await paginator_view.send(ctx)
Edit Existing Message
@bot.slash_command()
async def edit_pages(ctx):
# Send initial message
message = await ctx.respond("Loading paginator...")
# Create paginator
paginator_view = paginator.Paginator(pages=page_list)
# Edit the message with paginator
await paginator_view.edit(message, user=ctx.author)
Target Different Channel
@bot.slash_command()
async def send_to_channel(ctx, channel: discord.TextChannel):
"""Sends paginator to a different channel."""
paginator_view = paginator.Paginator(pages=page_list)
await paginator_view.respond(
ctx.interaction,
target=channel,
ephemeral=True # Response to user is ephemeral
)
Advanced Features
Updating Paginator
@bot.slash_command()
async def dynamic_pages(ctx):
"""Updates paginator after creation."""
initial_pages = ["Page 1", "Page 2"]
paginator_view = paginator.Paginator(pages=initial_pages)
await paginator_view.respond(ctx.interaction)
# Wait and update
await asyncio.sleep(5)
new_pages = ["Updated 1", "Updated 2", "New Page 3"]
await paginator_view.update(
pages=new_pages,
show_indicator=False,
current_page=0
)
Disabling and Cancelling
@bot.slash_command()
async def disable_example(ctx):
"""Shows disable and cancel functionality."""
paginator_view = paginator.Paginator(pages=page_list)
await paginator_view.respond(ctx.interaction)
await ctx.respond("Disabling in 5 seconds...")
await asyncio.sleep(5)
# Disable buttons but keep message
disable_page = discord.Embed(
title="Paginator Disabled",
description="This paginator is no longer active."
)
await paginator_view.disable(page=disable_page)
# Or cancel completely (remove buttons)
# await paginator_view.cancel(page=cancel_page)
Page Callbacks
class CallbackPage(Page):
async def callback(self, interaction=None):
"""Called when this page is displayed."""
print(f"Page displayed at {discord.utils.utcnow()}")
# You can perform actions here
@bot.slash_command()
async def callback_example(ctx):
pages_with_callbacks = [
CallbackPage(content="Page 1"),
CallbackPage(content="Page 2"),
CallbackPage(content="Page 3"),
]
paginator_view = paginator.Paginator(
pages=pages_with_callbacks,
trigger_on_display=True # Auto-trigger callbacks
)
await paginator_view.respond(ctx.interaction)
Complete Example
import discord
from discord.ext import commands, pages
import asyncio
bot = commands.Bot(command_prefix="!")
class PaginationCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
@discord.slash_command()
async def help(self, ctx):
"""Comprehensive help menu with pagination."""
# Define pages for different categories
general_embeds = [
discord.Embed(
title="General Commands - Page 1",
description="**!ping** - Check bot latency\n**!info** - Bot information",
color=discord.Color.blue()
),
discord.Embed(
title="General Commands - Page 2",
description="**!stats** - Server statistics\n**!about** - About the bot",
color=discord.Color.blue()
),
]
mod_embeds = [
discord.Embed(
title="Moderation Commands",
description="**!kick** - Kick a member\n**!ban** - Ban a member\n**!mute** - Mute a member",
color=discord.Color.red()
),
]
fun_embeds = [
discord.Embed(
title="Fun Commands",
description="**!meme** - Random meme\n**!joke** - Tell a joke",
color=discord.Color.green()
),
]
# Create page groups
page_groups = [
pages.PageGroup(
pages=general_embeds,
label="📋 General",
description="General bot commands",
default=True
),
pages.PageGroup(
pages=mod_embeds,
label="🔨 Moderation",
description="Moderation commands"
),
pages.PageGroup(
pages=fun_embeds,
label="🎉 Fun",
description="Fun and entertainment"
),
]
# Create custom buttons
custom_buttons = [
pages.PaginatorButton("first", emoji="⏮️"),
pages.PaginatorButton("prev", emoji="◀️"),
pages.PaginatorButton("page_indicator", style=discord.ButtonStyle.gray, disabled=True),
pages.PaginatorButton("next", emoji="▶️"),
pages.PaginatorButton("last", emoji="⏭️"),
]
# Create paginator
paginator_view = pages.Paginator(
pages=page_groups,
show_menu=True,
menu_placeholder="Select a category...",
use_default_buttons=False,
custom_buttons=custom_buttons,
timeout=180.0,
disable_on_timeout=True
)
await paginator_view.respond(ctx.interaction, ephemeral=False)
@commands.command()
async def search(self, ctx, *, query: str):
"""Search command with paginated results (prefix version)."""
# Simulate search results
results = [f"Result {i+1}: {query}" for i in range(10)]
# Create embeds
result_pages = [
discord.Embed(
title=f"Search Results for '{query}'",
description=results[i],
color=discord.Color.blue()
)
for i in range(len(results))
]
# Create paginator
paginator_view = pages.Paginator(
pages=result_pages,
loop_pages=True,
show_disabled=False
)
await paginator_view.send(ctx)
async def setup(bot):
await bot.add_cog(PaginationCog(bot))
bot.run("TOKEN")
Best Practices
Keep Pages Concise
Don’t overload individual pages with too much content. Break information into digestible chunks.
Use Page Groups for Categories
When you have different types of content, use PageGroup with a menu for better organization.
Set Appropriate Timeouts
Balance between user convenience and resource usage. Default is 180 seconds (3 minutes).
Ephemeral paginator responses cannot have a timeout of 15 minutes or greater due to Discord’s webhook token expiration.
For prefix commands, use
paginator.send(ctx) instead of paginator.respond(interaction).