Skip to main content
Proper error handling is crucial for creating reliable Discord bots. Pycord provides multiple ways to catch and handle errors.

Error Types

Command Errors

Errors that occur during command execution inherit from discord.ext.commands.CommandError:
  • CommandNotFound - Command doesn’t exist
  • MissingRequiredArgument - Required parameter not provided
  • BadArgument - Argument conversion failed
  • CheckFailure - Command check failed
  • CommandOnCooldown - Command is on cooldown
  • MissingPermissions - User lacks permissions
  • BotMissingPermissions - Bot lacks permissions
  • CommandInvokeError - Error during command execution

Application Command Errors

Errors for slash commands inherit from discord.ApplicationCommandError:
  • ApplicationCommandInvokeError - Error during slash command execution
  • CheckFailure - Check failed for application command

Discord API Errors

Errors from Discord API inherit from discord.DiscordException:
  • Forbidden - Missing permissions (403)
  • NotFound - Resource not found (404)
  • HTTPException - Generic HTTP error
  • ConnectionClosed - WebSocket connection closed

Global Error Handlers

Command Error Handler

Handle all prefix command errors:
from discord.ext import commands
import discord
import traceback

bot = commands.Bot(command_prefix="!")

@bot.event
async def on_command_error(ctx: commands.Context, error: commands.CommandError):
    """Global error handler for prefix commands."""
    
    # Get the original exception if wrapped
    if isinstance(error, commands.CommandInvokeError):
        error = error.original
    
    # Ignore commands not found
    if isinstance(error, commands.CommandNotFound):
        return
    
    # Handle specific error types
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send(f"❌ Missing required argument: `{error.param.name}`")
    
    elif isinstance(error, commands.MissingPermissions):
        perms = ", ".join(error.missing_permissions)
        await ctx.send(f"❌ You need these permissions: {perms}")
    
    elif isinstance(error, commands.BotMissingPermissions):
        perms = ", ".join(error.missing_permissions)
        await ctx.send(f"❌ I need these permissions: {perms}")
    
    elif isinstance(error, commands.CommandOnCooldown):
        await ctx.send(
            f"⏰ This command is on cooldown. Try again in {error.retry_after:.1f}s"
        )
    
    elif isinstance(error, commands.CheckFailure):
        await ctx.send("❌ You don't have permission to use this command.")
    
    elif isinstance(error, commands.BadArgument):
        await ctx.send(f"❌ Invalid argument: {error}")
    
    elif isinstance(error, discord.Forbidden):
        await ctx.send("❌ I don't have permission to do that.")
    
    elif isinstance(error, discord.NotFound):
        await ctx.send("❌ That resource was not found.")
    
    else:
        # Log unexpected errors
        print(f"Unexpected error in {ctx.command}:", file=sys.stderr)
        traceback.print_exception(type(error), error, error.__traceback__)
        await ctx.send("❌ An unexpected error occurred.")

Application Command Error Handler

Handle slash command errors:
bot = discord.Bot()

@bot.event
async def on_application_command_error(
    ctx: discord.ApplicationContext,
    error: discord.DiscordException
):
    """Global error handler for slash commands."""
    
    if isinstance(error, discord.ApplicationCommandInvokeError):
        error = error.original
    
    if isinstance(error, discord.CheckFailure):
        await ctx.respond(
            "❌ You don't have permission to use this command.",
            ephemeral=True
        )
    
    elif isinstance(error, discord.Forbidden):
        await ctx.respond(
            "❌ I don't have permission to do that.",
            ephemeral=True
        )
    
    elif isinstance(error, discord.NotFound):
        await ctx.respond(
            "❌ That resource was not found.",
            ephemeral=True
        )
    
    else:
        print(f"Unexpected error in {ctx.command}:", file=sys.stderr)
        traceback.print_exception(type(error), error, error.__traceback__)
        await ctx.respond(
            "❌ An unexpected error occurred.",
            ephemeral=True
        )

Local Error Handlers

Per-Command Error Handler

Handle errors for specific commands:
@bot.command()
async def divide(ctx: commands.Context, a: int, b: int):
    """Divide two numbers."""
    result = a / b
    await ctx.send(f"{a} / {b} = {result}")

@divide.error
async def divide_error(ctx: commands.Context, error: commands.CommandError):
    """Handle errors for divide command."""
    if isinstance(error, commands.CommandInvokeError):
        original = error.original
        if isinstance(original, ZeroDivisionError):
            await ctx.send("❌ Cannot divide by zero!")
        else:
            await ctx.send(f"❌ Error: {original}")
    else:
        # Re-raise to be handled by global handler
        raise error

Slash Command Error Handler

@bot.slash_command()
async def calculate(ctx: discord.ApplicationContext, operation: str, a: int, b: int):
    """Perform a calculation."""
    if operation == "add":
        result = a + b
    elif operation == "divide":
        result = a / b
    else:
        result = 0
    await ctx.respond(f"Result: {result}")

@calculate.error
async def calculate_error(ctx: discord.ApplicationContext, error: Exception):
    """Handle errors for calculate command."""
    if isinstance(error, discord.ApplicationCommandInvokeError):
        original = error.original
        if isinstance(original, ZeroDivisionError):
            await ctx.respond("❌ Cannot divide by zero!", ephemeral=True)
        else:
            raise error
    else:
        raise error

Cog Error Handlers

Handle errors for all commands in a cog:
class MyCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
    
    @commands.command()
    async def my_command(self, ctx: commands.Context):
        """A command that might fail."""
        # Command logic
        pass
    
    async def cog_command_error(
        self,
        ctx: commands.Context,
        error: commands.CommandError
    ):
        """Handle errors for all commands in this cog."""
        if isinstance(error, commands.CommandInvokeError):
            await ctx.send(f"❌ Something went wrong: {error.original}")
        else:
            # Let global handler deal with it
            raise error

Interaction Error Handling

Button/Select Menu Errors

class MyView(discord.ui.View):
    @discord.ui.button(label="Click Me", style=discord.ButtonStyle.primary)
    async def button_callback(
        self,
        button: discord.ui.Button,
        interaction: discord.Interaction
    ):
        try:
            # Your logic here
            await interaction.response.send_message("Success!")
        except discord.Forbidden:
            await interaction.response.send_message(
                "❌ I don't have permission to do that.",
                ephemeral=True
            )
        except Exception as e:
            await interaction.response.send_message(
                f"❌ An error occurred: {e}",
                ephemeral=True
            )
class MyModal(discord.ui.DesignerModal):
    def __init__(self, *args, **kwargs):
        # Modal setup
        super().__init__(*args, **kwargs)
    
    async def callback(self, interaction: discord.Interaction):
        try:
            # Process modal data
            value = self.children[0].item.value
            # Do something with value
            await interaction.response.send_message("Success!", ephemeral=True)
        except ValueError as e:
            await interaction.response.send_message(
                f"❌ Invalid input: {e}",
                ephemeral=True
            )
        except Exception as e:
            await interaction.response.send_message(
                f"❌ An error occurred: {e}",
                ephemeral=True
            )

Custom Exceptions

Create custom exceptions for your bot:
class MyBotError(commands.CommandError):
    """Base exception for bot errors."""
    pass

class UserBlacklisted(MyBotError):
    """Raised when a blacklisted user tries to use a command."""
    def __init__(self, user: discord.User):
        self.user = user
        super().__init__(f"{user} is blacklisted")

class DatabaseError(MyBotError):
    """Raised when a database operation fails."""
    pass

# Use custom exceptions
@bot.command()
async def admin_command(ctx: commands.Context):
    if ctx.author.id in BLACKLIST:
        raise UserBlacklisted(ctx.author)
    # Command logic

# Handle custom exceptions
@bot.event
async def on_command_error(ctx: commands.Context, error: commands.CommandError):
    if isinstance(error, UserBlacklisted):
        await ctx.send(f"❌ {error.user.mention} is blacklisted from using commands.")
    elif isinstance(error, DatabaseError):
        await ctx.send("❌ Database error. Please try again later.")
        # Log to admin channel
    else:
        # Handle other errors
        pass

Error Logging

Basic Logging

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('discord')

@bot.event
async def on_command_error(ctx: commands.Context, error: commands.CommandError):
    # Log all errors
    logger.error(
        f"Error in command {ctx.command}: {error}",
        exc_info=(type(error), error, error.__traceback__)
    )
    
    # Send user-friendly message
    await ctx.send("❌ An error occurred.")

Log to Channel

ERROR_LOG_CHANNEL_ID = 123456789

@bot.event
async def on_command_error(ctx: commands.Context, error: commands.CommandError):
    # Log to channel
    channel = bot.get_channel(ERROR_LOG_CHANNEL_ID)
    if channel:
        embed = discord.Embed(
            title="❌ Command Error",
            color=discord.Color.red(),
            timestamp=discord.utils.utcnow()
        )
        embed.add_field(name="Command", value=ctx.command.name)
        embed.add_field(name="User", value=f"{ctx.author} ({ctx.author.id})")
        embed.add_field(name="Channel", value=f"{ctx.channel.mention}")
        embed.add_field(name="Error", value=f"```{error}```", inline=False)
        
        # Add traceback for serious errors
        if isinstance(error, commands.CommandInvokeError):
            tb = "".join(traceback.format_exception(
                type(error.original),
                error.original,
                error.original.__traceback__
            ))
            # Truncate if too long
            if len(tb) > 1024:
                tb = tb[:1021] + "..."
            embed.add_field(name="Traceback", value=f"```py\n{tb}\n```", inline=False)
        
        await channel.send(embed=embed)

Graceful Degradation

Fallback Responses

@bot.command()
async def info(ctx: commands.Context, user: discord.User = None):
    """Get user info."""
    user = user or ctx.author
    
    try:
        # Try to get member object for rich info
        member = ctx.guild.get_member(user.id)
        if member:
            embed = discord.Embed(title=f"{member.display_name}")
            embed.add_field(name="Joined", value=member.joined_at)
            embed.add_field(name="Roles", value=len(member.roles))
            await ctx.send(embed=embed)
        else:
            # Fallback to basic user info
            await ctx.send(f"User: {user.mention}\nID: {user.id}")
    except Exception as e:
        # Ultimate fallback
        await ctx.send(f"Could not fetch info for {user.mention}")

Permission Handling

async def safe_send(channel: discord.TextChannel, content: str = None, **kwargs):
    """Safely send a message, handling permission errors."""
    try:
        return await channel.send(content, **kwargs)
    except discord.Forbidden:
        # Try to DM the author if we can't send in channel
        try:
            if hasattr(channel, 'guild'):
                # Get author from context if available
                return await channel.guild.owner.send(
                    f"I couldn't send a message in {channel.mention}: {content}"
                )
        except:
            pass
        # Silently fail if all else fails
        return None

Best Practices

1
Always handle errors
2
Never let exceptions crash your bot. Handle them gracefully.
3
Be user-friendly
4
Provide clear, helpful error messages to users.
5
Log everything
6
Log errors for debugging, but don’t expose sensitive info to users.
7
Test error paths
8
Test your error handlers to ensure they work correctly.
9
Use ephemeral responses
10
For errors in slash commands, use ephemeral=True to avoid cluttering the channel.
Security Concerns:
  • Never expose full tracebacks to users (attackers can use this info)
  • Don’t reveal system paths or configuration details in error messages
  • Be careful with logging sensitive data (tokens, user data, etc.)
  • Rate limit error messages to prevent spam

Common Patterns

Retry Logic

import asyncio
from functools import wraps

def retry(times=3, delay=1):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            for attempt in range(times):
                try:
                    return await func(*args, **kwargs)
                except Exception as e:
                    if attempt == times - 1:
                        raise
                    await asyncio.sleep(delay)
            return None
        return wrapper
    return decorator

@retry(times=3, delay=2)
async def fetch_data():
    # Might fail, will retry up to 3 times
    pass

Error Context Manager

from contextlib import asynccontextmanager

@asynccontextmanager
async def handle_errors(ctx: commands.Context):
    try:
        yield
    except discord.Forbidden:
        await ctx.send("❌ I don't have permission to do that.")
    except discord.HTTPException:
        await ctx.send("❌ Discord API error. Try again later.")
    except Exception as e:
        await ctx.send(f"❌ Error: {e}")

@bot.command()
async def my_command(ctx: commands.Context):
    async with handle_errors(ctx):
        # Your command logic
        pass

See Also

Build docs developers (and LLMs) love