Skip to main content
The 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

The Page 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 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
)

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

1

Keep Pages Concise

Don’t overload individual pages with too much content. Break information into digestible chunks.
2

Use Page Groups for Categories

When you have different types of content, use PageGroup with a menu for better organization.
3

Set Appropriate Timeouts

Balance between user convenience and resource usage. Default is 180 seconds (3 minutes).
4

Handle Ephemeral Responses

Ephemeral paginators must have a timeout less than 15 minutes.
5

Add Visual Polish

Use emojis, colors, and proper formatting to make paginated content more engaging.
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).

Build docs developers (and LLMs) love