discord-metadata
A Discord toolkit for building interactive metadata browsers with embeds and select-menu navigation.
Define your entities once and get Discord embeds with paginated sections, navigable links, and browsable search results. The Discord counterpart of rich-metadata.
Install
Requires Python 3.12+.
pip install discord-metadata
# or
uv add discord-metadata
Quick start
from discord_metadata import (
BaseNavigator,
DisplayEngine,
EntityDef,
HeaderField,
HeaderLink,
MetadataBot,
SectionDef,
SummaryField,
SyncAPI,
TableColumn,
)
# 1. Define entities
movie_def = EntityDef(
type_name="movie",
summary=[
SummaryField(key="title", bold=True),
SummaryField(key="year", prefix="(", transform=lambda v: f"{v})"),
],
header_fields=[
HeaderField("Director", key="director"),
HeaderField("Year", key="year"),
HeaderField("Genre", key="genre"),
],
sections=[
SectionDef(
"cast",
navigable=True,
columns=[
TableColumn("Actor", "name"),
TableColumn("Role", "role"),
],
),
SectionDef("synopsis"),
],
header_links=[
HeaderLink("Director: {director}", "person", ref_key="director_id"),
],
title_key="title",
color=0xE5A00D,
footer="Movie Database",
url_key="url",
)
# 2. Create engine and register definitions
engine = DisplayEngine()
engine.register(movie_def)
# 3. Wire up APIs and create bot
navigator = BaseNavigator(
engine,
apis={"movie": SyncAPI(MovieAPI())}, # or pass async APIs directly
)
bot = MetadataBot(navigator)
# 4. Register slash commands
@bot.tree.command(name="movie", description="Search for a movie")
async def cmd_movie(interaction, query: str):
await bot.navigator.search_and_navigate(interaction, query, ["movie"])
# 5. Run
bot.run_with_args("DISCORD_TOKEN")
See examples/movie_bot.py for a complete working example.
Concepts
EntityDef
Declares how an entity type is rendered as a Discord embed:
| Field | Purpose |
|---|---|
type_name |
Matches the _type key in data dicts |
summary |
One-line representation for search results and select menus |
header_fields |
Key-value fields shown in the embed header |
sections |
Named content blocks (text, tables, navigable lists) |
header_links |
Buttons that navigate to related entities |
image_url_key / thumbnail_url_key |
Embed image or thumbnail |
color / footer / url_key |
Embed appearance |
auto_full |
Show all sections at once (skip section menu; navigable rows become read-only) |
SectionDef
Each section can be:
- Text — a plain string value rendered in the embed description
- Table — a list of dicts rendered with
columns(list ofTableColumn) - Navigable — table rows become selectable items in the select menu
- Lazy — fetched on demand via a
lazy_fetchers={(type, section_key): fn}mapping passed toBaseNavigator(sync or async; sync receives the unwrapped API) - Numbered — items prefixed with
1.,2., etc. (default:True)
DisplayEngine
Registry of entity definitions. Call engine.register(def1, def2, ...) to add definitions.
BaseNavigator
The interactive browser. Main entry points:
| Method | Description |
|---|---|
search_and_navigate(interaction, query, types) |
Search APIs, show results, navigate on selection |
browse(interaction, fetch_page, title=...) |
Paginated browsing with a fetch_page(start, count) callable |
browse_sources(interaction, sources) |
Let the user pick between multiple browsable sources |
navigate_entity(interaction, entity) |
Display an entity directly |
navigate_results(interaction, results) |
Show a result list |
SyncAPI
Wraps a synchronous API (with .get(ref) and .search(query) methods) for safe
use in async Discord handlers via asyncio.to_thread.
apis = {"book": SyncAPI(BookAPI(client))}
Async APIs can be passed directly without wrapping.
MetadataBot
A ready-made discord.Client subclass that handles:
- Command tree setup and guild sync
.envloading via python-dotenv (optional)--guild GUILD_IDCLI argument for instant command sync during development- Cleanup callback via
on_close
bot = MetadataBot(navigator, on_close=client.close)
bot.run_with_args("DISCORD_TOKEN")
Navigator options
BaseNavigator(
engine,
apis={...},
entity_ref_key="url", # key used to fetch entity details (default: "url")
lazy_fetchers={ # optional: (entity_type, section_key) -> fn(api, entity)
("movie", "synopsis"): lambda api, entity: api.fetch_synopsis(entity["id"]),
},
ephemeral=False, # whether responses are ephemeral
placeholder="Browse...", # select menu placeholder text
search_kwargs={}, # extra kwargs passed to search methods
)
Paginated browsing
For APIs that return paginated results (discover, recent, upcoming, etc.),
use browse() with a fetch_page callable:
@bot.tree.command(name="recent")
async def cmd_recent(interaction):
await interaction.response.defer()
await navigator.browse(
interaction,
fetch_page=lambda start, count: api.fetch_page(start, count),
title="Recent releases",
)
fetch_page(start, count) must return (results: list[dict], total: int).
It can be sync or async.
Data format
Entity data is passed as plain dicts with a _type discriminator:
{
"_type": "movie",
"title": "Blade Runner",
"year": "1982",
"director": "Ridley Scott",
"cast": [
{"_type": "person", "name": "Harrison Ford", "role": "Deckard"},
],
}
Keys prefixed with _ (except _type) are internal and stripped from public output.
Development
git clone https://github.com/gabriel-jung/discord-metadata.git
cd discord-metadata
uv sync
License
MIT
Metadata
Release files for discord-metadata 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| discord_metadata-0.1.3.tar.gz | 14.5 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| discord_metadata-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.9 kB
Release files / discord_metadata-0.1.3.tar.gz
| Download URL | discord_metadata-0.1.3.tar.gz |
|---|---|
| Size | 14.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5ff8aedb8099d7363794ae6ee66281abb5478eacfe78af77c72c67df3cfc828a
|
|
BLAKE2b-256 checksum How to use checksums |
afdc9a87714a596012c05d0c45e78d550a88efbea1cd52d75a1107a8543f7646
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency logRelease files / discord_metadata-0.1.3-py3-none-any.whl
| Download URL | discord_metadata-0.1.3-py3-none-any.whl |
|---|---|
| Size | 16.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6567bc85d26262861f271e471b6c5b8e62ab53b927d9714e0682601aa99cb102
|
|
BLAKE2b-256 checksum How to use checksums |
6aa8e6a4084c569c6063bf82f141fae2b049855eb292c1a12e5e484e8c27a93c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency logSecurity for discord-metadata 0.1.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for discord_metadata-0.1.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.