A lightweight modern embed + Components V2 layout paginator for discord.py 2.6.0+ (2.7.1+ for cv2).
Project description
discord-pager
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
- Requirements
- Install
- Basic Usage
- Custom Views
- Per-Page Views
- Components V2
- API Reference
- Common Issues
- Contributing
- License
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.
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
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.Interactionorcommands.ext.commands.Context.pages:list[discord.Embed].view: optionaldiscord.ui.View, or alist[discord.ui.View]the same length aspagesfor per-page controls. Every item must setrowto 0-3; Pager's controls take the row right after the highest one used. RaisesPagerViewErrorif 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.Interactionorcommands.ext.commands.Context.pages:listof layout items, eg.list[discord.ui.Container].extra: optional extra components, either a single item / list of items shown on every page, or alist[list[discord.ui.Item]]the same length aspagesfor per-page extras. RaisesPagerLayoutErrorif 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6aeb234eee6c14d9b27577b4fc9184cffda678852937c511abf38ad0bf3c29f9
|
|
| MD5 |
a1064ab291de7e69210c184163e1d330
|
|
| BLAKE2b-256 |
ddac86e83f1d85349498bc71a7e460425339dba6be7df4b22095ee0459c1d028
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
686faed2f2688346d601f4f7aabc8557edfd6542606065038ee550237975315c
|
|
| MD5 |
e8f9543651586a7b3885283bc570848d
|
|
| BLAKE2b-256 |
4e96a2ab05fb21c88cf633aa8418bfcd154ec77ee056abcac860267d7d5c8c15
|