Skip to main content

A lightweight modern embed + Components V2 layout paginator for discord.py 2.6.0+ (2.7.1+ for cv2).

Project description

discord-pager

GitHub license Python 3.8+ Discord.py 2.6.0+ PRs welcome

A lightweight modern embed + Components V2 paginator for discord.py 2.6.0+ (2.7.1+ for cv2)

Yes!! You heard that right, Pager also supports the new Components V2 system (discord.ui.layout)!

Table of Contents

Showcase

Showcase 2

Showcase 3

Why discord-pager?

  • We do it all for you! All the burden of manual page navigation is removed when using Pager
  • CV2 Ready! Supports Discord's new Components V2 system unlike older paginators before this
  • Flexible! You can add custom buttons, per-page views, and extra components

Requirements

  • Python 3.8+
  • discord.py 2.6.0+ (2.7.1+ for Components V2 features)

Install

pip install discord-pager

or just copy the discord_pager/ folder into your bot's project if u want.

Basic usage

import discord
from discord.ext import commands
from discord_pager import Pager

bot = commands.Bot(command_prefix="pager!!!", intents=discord.Intents.default())


@bot.command()
async def pages(ctx: commands.Context):
    embeds = [
        discord.Embed(title="Page 1", description="This is the first page."),
        discord.Embed(title="Page 2", description="This is the second page."),
        discord.Embed(title="Page 3", description="This is the third page."),
    ]

    paginator = Pager()
    await paginator.start(ctx, embeds)

Merging in a custom view

Custom views can use rows 0-3 (4 rows max). Pager's own controls take whatever row comes right after the highest one you used, so they always end up on the last row instead of colliding with your layout.

Showcase

import discord
from discord.ext import commands
from discord_pager import Pager, PagerViewError

bot = commands.Bot(command_prefix="pager!!!", intents=discord.Intents.default())


@bot.command()
async def shop(ctx: commands.Context):
    embeds = [
        discord.Embed(title="Sword", description="A sharp blade. 100 gold."),
        discord.Embed(title="Shield", description="Sturdy protection. 80 gold."),
    ]

    custom_view = discord.ui.View(timeout=None)

    buy_button = discord.ui.Button(
        label="Buy", style=discord.ButtonStyle.green, row=0
    )

    async def buy_callback(interaction: discord.Interaction):
        await interaction.response.send_message("Purchased!", ephemeral=True)

    buy_button.callback = buy_callback
    custom_view.add_item(buy_button)

    wishlist_button = discord.ui.Button(
        label="Add to wishlist", style=discord.ButtonStyle.blurple, row=1
    )
    custom_view.add_item(wishlist_button)

    paginator = Pager()

    try:
        await paginator.start(ctx, embeds, view=custom_view)
    except PagerViewError as e:
        await ctx.send(f"Couldn't build the paginator: {e}")

Per-page custom views

If you want the custom controls to change alongside the embed (eg. a different "Buy" button and price per page), pass a list[discord.ui.View] the same length as pages (IMPORTANT!!!) instead of a single view. Pager swaps in the matching view's items every time the page changes:

import discord
from discord.ext import commands
from discord_pager import Pager

bot = commands.Bot(command_prefix="pager!!!", intents=discord.Intents.default())


@bot.command()
async def shop(ctx: commands.Context):
    items = [
        {"name": "Sword", "price": 100},
        {"name": "Shield", "price": 80},
        {"name": "Potion", "price": 15},
    ]

    embeds = [
        discord.Embed(title=item["name"], description=f"{item['price']} gold")
        for item in items
    ]

    views = []
    for item in items:
        v = discord.ui.View(timeout=None)
        buy_button = discord.ui.Button(
            label=f"Buy for {item['price']} gold",
            style=discord.ButtonStyle.green,
            row=0,
        )

        async def buy_callback(interaction: discord.Interaction, item=item):
            await interaction.response.send_message(
                f"Bought {item['name']}!", ephemeral=True
            )

        buy_button.callback = buy_callback
        v.add_item(buy_button)
        views.append(v)

    paginator = Pager()
    await paginator.start(ctx, embeds, view=views)

Each view in the list is validated the same way as a single static view (rows 0-3 only), and start() raises PagerViewError if the list's length doesn't match the number of pages. The controls row is based on the highest row used across all the views in the list, so it stays put as you page through instead of jumping around.

Components V2

Components v2

Discord's newer Components V2 system replaces embed and even content entirely with layout components (Container, TextDisplay, Section, etc), so it needs its own paginator: PagerCV2. It works the exact same way as Pager, just with discord.ui.Container (or any other layout item really) standing in for embeds, because a CV2 message can't send embeds at all.

You can also (if you are paging containers) pass controls_in_container=True into PagerCV2(...) in order to put the pagination controls inside the container

import discord
from discord.ext import commands
from discord_pager import PagerCV2

bot = commands.Bot(command_prefix="pager!!!", intents=discord.Intents.default())


class ContainerPage(discord.ui.Container):
    def __init__(self, name: str, price: int):
        super().__init__()
        self.add_item(discord.ui.TextDisplay(f"{name}"))


@bot.command()
async def pagescv2(ctx: commands.Context):
    pages = [
        ContainerPage("This is the first page."),
        ContainerPage("This is the second page."),
        ContainerPage("This is the third page."),
    ]

    paginator = PagerCV2()  # if u prefer the controls in the container, use controls_in_container=True
    await paginator.start(ctx, pages)

Merging in extra components

Just like Pager's view argument, pass extra to keep additional top-level components (eg. a Container with its own buttons) on screen alongside the paged item:

from discord_pager import PagerCV2, PagerLayoutError

extra_container = discord.ui.Container()
extra_row = discord.ui.ActionRow()


@extra_row.button(label="Add to wishlist", style=discord.ButtonStyle.blurple)
async def wishlist(interaction: discord.Interaction, button: discord.ui.Button):
    await interaction.response.send_message("Added!", ephemeral=True)


extra_container.add_item(extra_row)

paginator = PagerCV2()

try:
    await paginator.start(ctx, pages, extra=extra_container)
except PagerLayoutError as e:
    await ctx.send(f"Couldn't build the paginator: {e}")

extra can also be a list[list[discord.ui.Item]] the same length as pages, one list per page, the same way default Pager's view accepts a list[discord.ui.View] for per-page controls. Raises PagerLayoutError if that list's length doesn't match the number of pages. Unlike the classic Pager, there's no row 0-3 restriction to worry about here, since CV2 top-level components aren't laid out on the same 5-row grid buttons use.

API

Pager(...)

Parameter Type Default Description
timeout int 60 Seconds before the view times out.
previous_button discord.ui.Button ◀ grey button Overrides the default previous button.
next_button discord.ui.Button ▶ grey button Overrides the default next button.
page_counter_style discord.ButtonStyle discord.ButtonStyle.grey Style of the disabled page-counter button.
initial_page int 0 Page index to start on.
ephemeral bool False Whether the paginator message is ephemeral.
restrict_to_author bool False If True, only the original user can page through.

await Pager.start(source, pages, view=None)

  • source: discord.Interaction or commands.ext.commands.Context.
  • pages: list[discord.Embed].
  • view: optional discord.ui.View, or a list[discord.ui.View] the same length as pages for per-page controls. Every item must set row to 0-3; Pager's controls take the row right after the highest one used. Raises PagerViewError if a row is invalid, or if a list of views doesn't match the number of pages.

PagerCV2(...)

Same parameters as Pager almost

Parameter Type Default Description
timeout int 60 Seconds before the view times out.
previous_button discord.ui.Button ◀ grey button Overrides the default previous button.
next_button discord.ui.Button ▶ grey button Overrides the default next button.
page_counter_style discord.ButtonStyle discord.ButtonStyle.grey Style of the disabled page-counter button.
initial_page int 0 Page index to start on.
ephemeral bool False Whether the paginator message is ephemeral.
restrict_to_author bool False If True, only the original user can page through.
controls_in_container bool False If True, controls will be inside the passed containers (if any).

await PagerCV2.start(source, pages, extra=None)

  • source: discord.Interaction or commands.ext.commands.Context.
  • pages: list of layout items, eg. list[discord.ui.Container].
  • extra: optional extra components, either a single item / list of items shown on every page, or a list[list[discord.ui.Item]] the same length as pages for per-page extras. Raises PagerLayoutError if a list of per-page extras doesn't match the number of pages.

Common Issues

PagerViewError: Invalid row

Custom views can only use rows 0-3 (meaning max 4 rows). Pager automatically uses the next available row for its controls.

PagerViewError: View list length doesn't match pages

When passing per-page views, the list must have the same length as your pages.

PagerLayoutError: Extra list length doesn't match pages

Same as above, but for CV2 per-page extras.

My CV2 message isn't showing

Make sure you're using PagerCV2 (not Pager!!!!!) and passing layout items like discord.ui.Container, not embeds! You cannot have embeds in CV2 messages

Contributing

Contributions are welcome! Feel free to open issues and PRs as you like.

Guidelines:

  • Make sure your code is formatted by yapf

That's all!

License

This project is licensed under the MIT License. See the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

discord_pager-0.1.0.tar.gz (13.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

discord_pager-0.1.0-py3-none-any.whl (13.0 kB view details)

Uploaded Python 3

File details

Details for the file discord_pager-0.1.0.tar.gz.

File metadata

  • Download URL: discord_pager-0.1.0.tar.gz
  • Upload date:
  • Size: 13.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for discord_pager-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6aeb234eee6c14d9b27577b4fc9184cffda678852937c511abf38ad0bf3c29f9
MD5 a1064ab291de7e69210c184163e1d330
BLAKE2b-256 ddac86e83f1d85349498bc71a7e460425339dba6be7df4b22095ee0459c1d028

See more details on using hashes here.

File details

Details for the file discord_pager-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: discord_pager-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for discord_pager-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 686faed2f2688346d601f4f7aabc8557edfd6542606065038ee550237975315c
MD5 e8f9543651586a7b3885283bc570848d
BLAKE2b-256 4e96a2ab05fb21c88cf633aa8418bfcd154ec77ee056abcac860267d7d5c8c15

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page