A modern, feature-rich, and async-ready API wrapper for Discord written in Python
—
Discord's native slash commands and context menus with built-in parameter validation, autocomplete, localization, and seamless UI integration. Application commands appear in Discord's interface and provide a modern alternative to prefix-based commands.
The CommandTree manages registration and syncing of application commands with Discord's API.
class CommandTree:
"""
Manages application commands for a client.
"""
def __init__(self, client: Client): ...
client: Client # Associated Discord client
async def sync(self, *, guild: Optional[Guild] = None) -> List[AppCommand]:
"""
Sync commands with Discord.
Parameters:
- guild: Guild to sync to (None for global commands)
Returns:
List of synced AppCommand objects
"""
async def fetch_command(self, command_id: int, *, guild: Optional[Guild] = None) -> AppCommand:
"""Fetch a command by ID."""
async def fetch_commands(self, *, guild: Optional[Guild] = None) -> List[AppCommand]:
"""Fetch all commands."""
def command(
self,
*,
name: str = None,
description: str = None,
nsfw: bool = False,
guild: Optional[Guild] = None,
guilds: Optional[List[Guild]] = None,
auto_locale_strings: bool = True
) -> Callable:
"""Decorator to register a slash command."""
def context_menu(
self,
*,
name: str = None,
nsfw: bool = False,
guild: Optional[Guild] = None,
guilds: Optional[List[Guild]] = None,
auto_locale_strings: bool = True
) -> Callable:
"""Decorator to register a context menu command."""
def add_command(self, command: Union[Command, ContextMenu], *, guild: Optional[Guild] = None, guilds: Optional[List[Guild]] = None, override: bool = False) -> None:
"""Add a command to the tree."""
def remove_command(self, command: Union[str, Command, ContextMenu], *, guild: Optional[Guild] = None, type: Optional[AppCommandType] = None) -> Optional[Union[Command, ContextMenu]]:
"""Remove a command from the tree."""
def get_command(self, name: str, *, guild: Optional[Guild] = None, type: Optional[AppCommandType] = None) -> Optional[Union[Command, ContextMenu]]:
"""Get a command by name."""
def get_commands(self, *, guild: Optional[Guild] = None, type: Optional[AppCommandType] = None) -> List[Union[Command, ContextMenu]]:
"""Get all commands."""
def walk_commands(self, *, guild: Optional[Guild] = None, type: Optional[AppCommandType] = None) -> Iterator[Union[Command, ContextMenu]]:
"""Walk all commands."""
def clear_commands(self, *, guild: Optional[Guild] = None, type: Optional[AppCommandType] = None) -> None:
"""Clear all commands."""
def copy_global_to(self, guild: Guild) -> None:
"""Copy global commands to a guild."""
async def set_translator(self, translator: Translator) -> None:
"""Set command translator for localization."""
def error(self, coro: Callable) -> Callable:
"""Decorator for global error handler."""
def interaction_check(self, coro: Callable) -> Callable:
"""Decorator for global interaction check."""Slash commands provide native Discord UI integration with parameter validation and autocomplete.
class Command:
"""
Represents a slash command.
"""
def __init__(
self,
*,
name: str,
description: str,
callback: Callable,
nsfw: bool = False,
parent: Optional[Group] = None,
guild_ids: Optional[List[int]] = None,
auto_locale_strings: bool = True,
extras: Dict[Any, Any] = None
): ...
name: str # Command name
description: str # Command description
callback: Callable # Command function
nsfw: bool # Whether command is NSFW
parent: Optional[Group] # Parent group
guild_ids: Optional[List[int]] # Restricted guild IDs
qualified_name: str # Full command name including parent
mention: str # Command mention string
parameters: List[Parameter] # Command parameters
checks: List[Callable] # Command checks
extras: Dict[Any, Any] # Extra metadata
async def __call__(self, interaction: Interaction, **parameters) -> Any:
"""Invoke the command."""
def error(self, coro: Callable) -> Callable:
"""Decorator for command error handler."""
def autocomplete(self, name: str) -> Callable:
"""Decorator for parameter autocomplete."""
def describe(self, **parameters: str) -> Command:
"""Add parameter descriptions."""
def rename(self, **parameters: str) -> Command:
"""Rename parameters."""
class Group(Command):
"""
Slash command group containing subcommands.
"""
def __init__(self, *, name: str, description: str, **kwargs): ...
commands: Dict[str, Union[Command, Group]] # Child commands
def command(
self,
*,
name: str = None,
description: str = None,
nsfw: bool = False,
auto_locale_strings: bool = True,
extras: Dict[Any, Any] = None
) -> Callable:
"""Decorator to add a subcommand."""
def group(
self,
*,
name: str = None,
description: str = None,
nsfw: bool = False,
auto_locale_strings: bool = True,
extras: Dict[Any, Any] = None
) -> Callable:
"""Decorator to add a subcommand group."""
def add_command(self, command: Command) -> None:
"""Add a subcommand."""
def remove_command(self, name: str) -> Optional[Command]:
"""Remove a subcommand."""
def get_command(self, name: str) -> Optional[Command]:
"""Get a subcommand by name."""
# Command decorators
def command(
*,
name: str = None,
description: str = None,
nsfw: bool = False,
guild: Optional[Guild] = None,
guilds: Optional[List[Guild]] = None,
auto_locale_strings: bool = True,
extras: Dict[Any, Any] = None
) -> Callable:
"""Decorator to create a slash command."""
def context_menu(
*,
name: str = None,
nsfw: bool = False,
guild: Optional[Guild] = None,
guilds: Optional[List[Guild]] = None,
auto_locale_strings: bool = True,
extras: Dict[Any, Any] = None
) -> Callable:
"""Decorator to create a context menu command."""
def describe(**parameters: str) -> Callable:
"""Decorator to add parameter descriptions."""
def rename(**parameters: str) -> Callable:
"""Decorator to rename parameters."""
def choices(**parameters: List[Choice]) -> Callable:
"""Decorator to add parameter choices."""
def autocomplete(**parameters: Callable) -> Callable:
"""Decorator to add parameter autocomplete."""
def guild_only() -> Callable:
"""Decorator to restrict command to guilds only."""
def guilds(*guild_ids: int) -> Callable:
"""Decorator to restrict command to specific guilds."""
def default_permissions(**permissions: bool) -> Callable:
"""Decorator to set default permissions."""Context menu commands appear in right-click menus for users and messages.
class ContextMenu:
"""
Represents a context menu command.
"""
def __init__(
self,
*,
name: str,
callback: Callable,
nsfw: bool = False,
guild_ids: Optional[List[int]] = None,
auto_locale_strings: bool = True,
extras: Dict[Any, Any] = None
): ...
name: str # Command name
type: AppCommandType # Command type (USER or MESSAGE)
callback: Callable # Command function
nsfw: bool # Whether command is NSFW
guild_ids: Optional[List[int]] # Restricted guild IDs
qualified_name: str # Command name
mention: str # Command mention string
checks: List[Callable] # Command checks
extras: Dict[Any, Any] # Extra metadata
async def __call__(self, interaction: Interaction, target: Union[User, Member, Message]) -> Any:
"""Invoke the context menu command."""
def error(self, coro: Callable) -> Callable:
"""Decorator for command error handler."""Command parameters with type validation, choices, and autocomplete support.
class Parameter:
"""
Represents a command parameter.
"""
def __init__(
self,
*,
name: str,
description: str,
type: AppCommandOptionType,
required: bool = True,
default: Any = None,
choices: Optional[List[Choice]] = None,
autocomplete: Optional[Callable] = None,
channel_types: Optional[List[ChannelType]] = None,
min_value: Optional[Union[int, float]] = None,
max_value: Optional[Union[int, float]] = None,
min_length: Optional[int] = None,
max_length: Optional[int] = None
): ...
name: str # Parameter name
display_name: str # Display name (localized)
description: str # Parameter description
type: AppCommandOptionType # Parameter type
required: bool # Whether parameter is required
default: Any # Default value
choices: Optional[List[Choice]] # Fixed choices
autocomplete: Optional[Callable] # Autocomplete function
channel_types: Optional[List[ChannelType]] # Allowed channel types
min_value: Optional[Union[int, float]] # Minimum numeric value
max_value: Optional[Union[int, float]] # Maximum numeric value
min_length: Optional[int] # Minimum string length
max_length: Optional[int] # Maximum string length
class Choice:
"""
Represents a parameter choice.
"""
def __init__(self, *, name: str, value: Union[str, int, float]): ...
name: str # Choice display name
value: Union[str, int, float] # Choice value
class Range:
"""
Type annotation for numeric parameter ranges.
Usage: value: app_commands.Range[int, 1, 100]
"""
def __init__(self, annotation: Type, min: Any = None, max: Any = None): ...
# Parameter transformers
class Transform:
"""
Transform parameter values with custom logic.
Usage: value: app_commands.Transform[CustomType, CustomTransformer]
"""
def __init__(self, annotation: Type, transformer: Type[Transformer]): ...
class Transformer:
"""
Base class for parameter transformers.
"""
async def transform(self, interaction: Interaction, value: Any) -> Any:
"""Transform parameter value."""
raise NotImplementedError
async def autocomplete(self, interaction: Interaction, value: Union[int, float, str]) -> List[Choice]:
"""Provide autocomplete choices."""
return []Interaction objects represent slash command invocations with response management.
class Interaction:
"""
Represents a Discord interaction (slash command, button click, etc.).
"""
id: int # Interaction ID
application_id: int # Application ID
type: InteractionType # Interaction type
data: Optional[InteractionData] # Interaction data
guild_id: Optional[int] # Guild ID
guild: Optional[Guild] # Guild object
channel_id: Optional[int] # Channel ID
channel: Optional[Union[GuildChannel, PartialMessageable]] # Channel object
user: User # User who triggered interaction
member: Optional[Member] # Member object if in guild
token: str # Interaction token
version: int # Interaction version
message: Optional[Message] # Message for component interactions
followup: Followup # Followup webhook
response: InteractionResponse # Response manager
command: Optional[Union[Command, ContextMenu]] # Command object
namespace: Optional[Namespace] # Parameter namespace
locale: str # User locale
guild_locale: Optional[str] # Guild locale
created_at: datetime # Interaction creation time
expires_at: datetime # When interaction expires
@property
def client(self) -> Client:
"""Client that received the interaction."""
def is_expired(self) -> bool:
"""Check if interaction has expired."""
async def original_response(self) -> InteractionMessage:
"""Get the original response message."""
async def edit_original_response(self, **kwargs) -> InteractionMessage:
"""Edit the original response."""
async def delete_original_response(self) -> None:
"""Delete the original response."""
async def translate(self, string: locale_str, locale: str = None, **kwargs) -> str:
"""Translate a localizable string."""
class InteractionResponse:
"""
Manages interaction responses.
"""
def __init__(self, parent: Interaction): ...
is_done(self) -> bool:
"""Check if response has been sent."""
async def send_message(
self,
content: str = None,
*,
embed: Embed = None,
embeds: List[Embed] = None,
file: File = None,
files: List[File] = None,
view: View = None,
ephemeral: bool = False,
tts: bool = False,
allowed_mentions: AllowedMentions = None,
suppress_embeds: bool = False
) -> None:
"""Send response message."""
async def defer(self, *, ephemeral: bool = False, thinking: bool = False) -> None:
"""Defer response to send later."""
async def pong(self) -> None:
"""Respond to ping interaction."""
async def edit_message(
self,
*,
content: str = None,
embed: Embed = None,
embeds: List[Embed] = None,
attachments: List[Attachment] = None,
view: View = None,
allowed_mentions: AllowedMentions = None,
suppress_embeds: bool = False
) -> None:
"""Edit original message."""
async def autocomplete(self, *, choices: List[Choice]) -> None:
"""Respond with autocomplete choices."""
class Namespace:
"""
Container for command parameters.
"""
def __init__(self, interaction: Interaction, data: InteractionData): ...
def __getattr__(self, name: str) -> Any:
"""Get parameter value by name."""
def __getitem__(self, name: str) -> Any:
"""Get parameter value by name."""
def __contains__(self, name: str) -> bool:
"""Check if parameter exists."""
def __iter__(self) -> Iterator[str]:
"""Iterate parameter names."""
def get(self, name: str, default: Any = None) -> Any:
"""Get parameter value with default."""Permission validation and custom checks for application commands.
# Check decorators
def check(predicate: Callable[[Interaction], Awaitable[bool]]) -> Callable:
"""Add a check to a command."""
# Permission checks
def has_role(role: Union[int, str]) -> Callable:
"""Check if user has a specific role."""
def has_any_role(*roles: Union[int, str]) -> Callable:
"""Check if user has any of the specified roles."""
def has_permissions(**permissions: bool) -> Callable:
"""Check if user has specific permissions."""
def bot_has_permissions(**permissions: bool) -> Callable:
"""Check if bot has specific permissions."""
def default_permissions(**permissions: bool) -> Callable:
"""Set default permissions for the command."""
# Cooldown system
class Cooldown:
"""
Application command cooldown.
"""
def __init__(self, rate: int, per: float): ...
rate: int # Number of uses allowed
per: float # Time period in seconds
def cooldown(rate: int, per: float) -> Callable:
"""Add cooldown to a command."""
def dynamic_cooldown(cooldown_func: Callable[[Interaction], Optional[Cooldown]]) -> Callable:
"""Add dynamic cooldown to a command."""Comprehensive error handling for application commands with specific exception types.
# Base application command errors
class AppCommandError(DiscordException):
"""Base exception for application command errors."""
pass
class CommandInvokeError(AppCommandError):
"""Exception occurred during command execution."""
def __init__(self, command: Union[Command, ContextMenu], e: Exception): ...
command: Union[Command, ContextMenu]
original: Exception
# Parameter errors
class TransformerError(AppCommandError):
"""Parameter transformer failed."""
def __init__(self, value: Any, type: AppCommandOptionType, transformer: Type[Transformer]): ...
value: Any
type: AppCommandOptionType
transformer: Type[Transformer]
# Command management errors
class CommandAlreadyRegistered(AppCommandError):
"""Command is already registered."""
def __init__(self, name: str, guild_id: Optional[int]): ...
name: str
guild_id: Optional[int]
class CommandSignatureMismatch(AppCommandError):
"""Command signature doesn't match Discord's requirements."""
pass
class CommandNotFound(AppCommandError):
"""Command was not found."""
def __init__(self, name: str, parents: List[str]): ...
name: str
parents: List[str]
class CommandLimitReached(AppCommandError):
"""Maximum number of commands reached."""
def __init__(self, guild_id: Optional[int], limit: int, type: AppCommandType): ...
guild_id: Optional[int]
limit: int
type: AppCommandType
# Check failures
class CheckFailure(AppCommandError):
"""Command check failed."""
pass
class NoPrivateMessage(CheckFailure):
"""Command cannot be used in private messages."""
pass
class MissingRole(CheckFailure):
"""User is missing required role."""
def __init__(self, missing_role: Union[str, int]): ...
missing_role: Union[str, int]
class MissingAnyRole(CheckFailure):
"""User is missing any of the required roles."""
def __init__(self, missing_roles: List[Union[str, int]]): ...
missing_roles: List[Union[str, int]]
class MissingPermissions(CheckFailure):
"""User is missing required permissions."""
def __init__(self, missing_permissions: List[str]): ...
missing_permissions: List[str]
class BotMissingPermissions(CheckFailure):
"""Bot is missing required permissions."""
def __init__(self, missing_permissions: List[str]): ...
missing_permissions: List[str]
class CommandOnCooldown(CheckFailure):
"""Command is on cooldown."""
def __init__(self, cooldown: Cooldown, retry_after: float): ...
cooldown: Cooldown
retry_after: float
# Sync errors
class CommandSyncFailure(AppCommandError):
"""Failed to sync commands with Discord."""
pass
class MissingApplicationID(CommandSyncFailure):
"""Application ID is missing."""
pass
# Translation errors
class TranslationError(AppCommandError):
"""Translation failed."""
def __init__(self, message: str, key: locale_str, locale: str): ...
message: str
key: locale_str
locale: strBuilt-in localization system for command names, descriptions, and responses.
class locale_str:
"""
Represents a localizable string.
"""
def __init__(self, message: str, **kwargs): ...
message: str # Base message string
extras: Dict[str, Any] # Extra data for formatting
class Translator:
"""
Base class for command translators.
"""
async def translate(self, string: locale_str, locale: str, context: TranslationContext) -> Optional[str]:
"""Translate a localizable string."""
raise NotImplementedError
class TranslationContext:
"""
Context for translation requests.
"""
location: TranslationContextLocation # Where translation is used
data: Any # Context-specific data
class TranslationContextLocation(Enum):
"""Location where translation is being performed."""
command_name = 1
command_description = 2
parameter_name = 3
parameter_description = 4
choice_name = 5
other = 6
TranslationContextTypes = Union[
Command,
ContextMenu,
Parameter,
Choice,
Group
]import discord
from discord.ext import commands
class MyBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
super().__init__(command_prefix='!', intents=intents)
async def setup_hook(self):
# Sync commands on startup
await self.tree.sync()
print(f"Synced {len(self.tree.get_commands())} commands")
bot = MyBot()
@bot.tree.command(name="hello", description="Say hello to someone")
@app_commands.describe(user="The user to greet")
async def hello(interaction: discord.Interaction, user: discord.Member):
await interaction.response.send_message(f"Hello {user.mention}!")
bot.run('YOUR_TOKEN')@bot.tree.command(name="roll", description="Roll dice")
@app_commands.describe(
sides="Number of sides on the die",
count="Number of dice to roll"
)
@app_commands.choices(sides=[
app_commands.Choice(name="4-sided (d4)", value=4),
app_commands.Choice(name="6-sided (d6)", value=6),
app_commands.Choice(name="8-sided (d8)", value=8),
app_commands.Choice(name="10-sided (d10)", value=10),
app_commands.Choice(name="12-sided (d12)", value=12),
app_commands.Choice(name="20-sided (d20)", value=20),
])
async def roll_dice(
interaction: discord.Interaction,
sides: app_commands.Choice[int],
count: app_commands.Range[int, 1, 10] = 1
):
import random
rolls = [random.randint(1, sides.value) for _ in range(count)]
total = sum(rolls)
embed = discord.Embed(title="🎲 Dice Roll Results", color=0x00ff00)
embed.add_field(name="Rolls", value=" + ".join(map(str, rolls)), inline=False)
embed.add_field(name="Total", value=str(total), inline=True)
embed.add_field(name="Dice", value=f"{count}d{sides.value}", inline=True)
await interaction.response.send_message(embed=embed)@bot.tree.context_menu(name="Get User Info")
async def user_info(interaction: discord.Interaction, user: discord.Member):
embed = discord.Embed(title=f"Info for {user.display_name}", color=user.color)
embed.set_thumbnail(url=user.display_avatar.url)
embed.add_field(name="ID", value=user.id, inline=True)
embed.add_field(name="Joined", value=f"<t:{int(user.joined_at.timestamp())}:R>", inline=True)
embed.add_field(name="Roles", value=len(user.roles) - 1, inline=True)
await interaction.response.send_message(embed=embed, ephemeral=True)@bot.tree.command(name="config", description="Bot configuration commands")
async def config_group(interaction: discord.Interaction):
# This won't be called as it has subcommands
pass
@config_group.subcommand(name="prefix", description="Set bot prefix")
@app_commands.describe(new_prefix="New command prefix")
async def config_prefix(interaction: discord.Interaction, new_prefix: str):
# Update prefix logic here
await interaction.response.send_message(f"Prefix set to `{new_prefix}`")
@config_group.subcommand(name="channel", description="Set bot channel")
@app_commands.describe(channel="Channel for bot commands")
async def config_channel(interaction: discord.Interaction, channel: discord.TextChannel):
# Update channel logic here
await interaction.response.send_message(f"Bot channel set to {channel.mention}")# Simple autocomplete
@bot.tree.command(name="timezone", description="Set your timezone")
async def timezone_command(interaction: discord.Interaction, timezone: str):
# Timezone setting logic here
await interaction.response.send_message(f"Timezone set to {timezone}")
@timezone_command.autocomplete('timezone')
async def timezone_autocomplete(interaction: discord.Interaction, current: str) -> List[app_commands.Choice[str]]:
timezones = ["UTC", "US/Eastern", "US/Central", "US/Mountain", "US/Pacific", "Europe/London", "Europe/Berlin"]
return [
app_commands.Choice(name=tz, value=tz)
for tz in timezones if current.lower() in tz.lower()
][:25] # Discord limits to 25 choicesInstall with Tessl CLI
npx tessl i tessl/pypi-discord-py