Skip to main content

Error Handling

About

When using the library, errors are frequently raised due to various reasons. These reasons range from the Discord API failing your bot's request due to missing permissions or an invalid syntax error.

Before going into error handling, it's best that you learn the basic ways of handling errors. A good resource is:

Basic Handling

The most basic way to handle errors is to tackle it at the source. This can be done like so:

./main.py
# This code requires the `message_content` privileged intent for prefixed commands.

import asyncio

import discord

intents = discord.Intents.default()
intents.message_content = True

bot = discord.Bot(intents=intents)


@bot.slash_command(description="Gets some feedback.")
@discord.option("name", description="Enter your name.")
async def feedback(ctx: discord.ApplicationContext, name: str):
try:
await ctx.respond(f"Hey, {name}! Send your feedback within the next 30 seconds please!")

def is_author(m: discord.Message):
return m.author.id == ctx.author.id

feedback_message = await bot.wait_for("message", check=is_author, timeout=30.0)
await ctx.send(f"Thanks for the feedback!\nReceived feedback: `{feedback_message.content}`")
except asyncio.TimeoutError:
await ctx.send("Timed out, please try again!")


bot.run("TOKEN")

If you respond within 30 seconds, the interaction will look like so:

Dorukyumused /feedback
RobocordBot04/18/2024
Hey, Dorukyum! Send your feedback within the next 30 seconds please!
Dorukyum04/18/2024
Great bot!
RobocordBot04/18/2024
Thanks for the feedback!
Received feedback: Great bot!

Otherwise, if you don't respond in time, the interaction will go as follows:

Dorukyumused /feedback
RobocordBot04/18/2024
Hey, Dorukyum! Send your feedback within the next 30 seconds please!
RobocordBot04/18/2024
Timed out, please try again!

This basic method of error handling can be extended to handle many other errors including ones raised directly by py-cord.

Per-Command Handling

This type of error handling allows you to handle errors per each command you have on your bot. Each and every command can have it's own error handler made to handle specific errors that each command may raise!

An example of per-command error handling is as follows:

Bridge Commands

As of writing this guide page, bridge commands do not currently support per-command error handling.

For these types of commands, it is recommended to utilize global error handling until the feature is added.

./main.py
import discord
from discord.ext import commands

bot = discord.Bot(owner_id=...) # Your Discord user ID goes in owner_id


@bot.slash_command(description="A private command...")
@commands.is_owner() # This decorator will raise commands.NotOwner if the invoking user doesn't have the owner_id
async def secret(ctx: discord.ApplicationContext):
await ctx.respond(f"Hey {ctx.author.name}! This is a secret command!")


@secret.error
async def on_application_command_error(ctx: discord.ApplicationContext, error: discord.DiscordException):
if isinstance(error, commands.NotOwner):
await ctx.respond("Sorry, only the bot owner can use this command!")
else:
raise error # Here we raise other errors to ensure they aren't ignored


bot.run("TOKEN")

If your ID is registered as the owner ID, you'll get the following:

Dorukyumused /secret
RobocordBot04/18/2024
Hey Dorukyum! This is a secret command!

Any other user whose ID doesn't match the bot owner's will get the following:

BobDotComused /secret
RobocordBot04/18/2024
Sorry, only the bot owner can use this command!

This local (per-command) error handler can also be used to handle the same types of errors that standard try-except statements can handle. This is done by using the same method as above with the isinstance built-in function.

Per-Cog Handling

Adding error handlers per-command can be quite the task in terms of work if you have a lot. If you have happened to group your commands in cogs, then you're in luck! You can create an error handler that is specific to a cog and handles all errors raised by commands inside that cog.

Here's an example of a bot with a cog that implements its own error handling:

./cogs/dm.py
# This cog is for DM only commands! Sadly, we only have 1 command here right now...

import discord
from discord.ext import commands


class DM(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot

@commands.command()
@commands.dm_only() # This decorator will raise commands.PrivateMessageOnly if invoked in a guild context.
async def avatar(self, ctx: commands.Context):
embed = discord.Embed(
title="Avatar",
description=f"Here's your enlarged avatar, {ctx.author.name}!",
color=ctx.author.top_role.color
)
embed.set_image(url=ctx.author.display_avatar.url)
await ctx.send(embed=embed, reference=ctx.message)

async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError):
if isinstance(error, commands.PrivateMessageOnly):
await ctx.send("Sorry, you can only use this in private messages!", reference=ctx.message)
else:
raise error # Here we raise other errors to ensure they aren't ignored


def setup(bot: commands.Bot):
bot.add_cog(DM(bot))
./main.py
# This code requires the `message_content` privileged intent for prefixed commands.

# This is the main file where we load the DM cog and run the bot.

import discord
from discord.ext import commands

intents = discord.Intents.default()
intents.message_content = True

bot = commands.Bot(command_prefix=commands.when_mentioned_or("!"))
bot.load_extension("cogs.dm")
bot.run("TOKEN")

If you use this command in a DM context, you'll get the following:

BobDotCom04/18/2024
!avatar
@BobDotCom!avatar
RobocordBot04/18/2024
Avatar
Here's your enlarged avatar, BobDotCom!

Otherwise, if used in a guild context:

Dorukyum04/18/2024
!avatar
@Dorukyum!avatar
RobocordBot04/18/2024
Sorry, you can only use this in private messages!

Per-cog error handling comes in very handy as you can relegate all of your error handling to a single function instead of spreading it out across several per-command error handlers or inside the commands themselves.

Global Handling

If separating your error handling is not how you would like to handle errors, then global error handling is the way to go. This method of handling allows you to relegate all handling to a single function that resides within your bot instance.

A non-subclassed bot would implement this like so:

./main.py
import discord
from discord.ext import commands

bot = discord.Bot()


@bot.slash_command(description="Get the bot's current latency!")
@commands.cooldown(1, 30, commands.BucketType.user)
# ^ This decorator allows one usage of the command every 30 seconds and raises commands.CommandOnCooldown if exceeded
async def ping(ctx: discord.ApplicationContext):
await ctx.respond(f"Pong! `{int(bot.latency*1000)} ms`.")


@bot.event
async def on_application_command_error(ctx: discord.ApplicationContext, error: discord.DiscordException):
if isinstance(error, commands.CommandOnCooldown):
await ctx.respond("This command is currently on cooldown!")
else:
raise error # Here we raise other errors to ensure they aren't ignored


bot.run("TOKEN")

If you've subclassed your bot, the on_application_command_error event will be placed inside the subclass without a bot.event decorator and bot.slash_command will be replaced with discord.slash_command.

The error handling used above will yield this interaction if the command is used again too quickly:

BobDotComused /ping
RobocordBot04/18/2024
Pong! 49 ms.
BobDotComused /ping
RobocordBot04/18/2024
This command is currently on cooldown!

The only issue regarding global error handling is that, if you have a large amount of commands, the global handler may start to get crammed with a lot of code. This is where all the previously mentioned handlers can take their place!

FAQ

Why does the example in per-cog handling have self at the start of its function signatures?

This is a feature of classes and allows you to reference the cog object as self and get access to the bot object passed at initialization through self.bot.

If this is new to you, we recommend checking out these helpful resources to learn more about classes and cogs:

How many errors can I handle in a single error handler?

While all the examples in this guide page only handle one specific error and re-raise all others, all of these error handlers can be extended to catch many errors.

In basic handling (i.e. try/except statements) you can do one of two things:

  • Explicitly handle the errors you know might get raised and then have a broad except Exception as exc: statement as a catch-all.
  • Catch any and all errors via the catch-all statement mentioned above.

In per-command, per-cog, and global handling, you can use elif clauses with the built-in isinstance function to catch specific errors. Atop of this, an else clause can be used to catch any other errors you either don't want to handle or want to relegate to a different error handler.

Can I use more than one type of error handler at once?

Thankfully, all of these different ways of error handling can be mixed together, so you don't have to pick just one! For commands that raise specific errors that no other commands will raise, you can use per-command. For cogs that raise specific errors that no other cogs will raise, use per-cog! Lastly, for errors that occur frequently across all commands and cogs, use global handling!

However, one important note to remember is that you should always raise the error again in an error handler if it isn't handled there! If you don't do this, the error will go ignored and your other handlers won't have a chance to do their work!

What's the difference between slash and prefixed command error handling?

Although most examples shown here use slash commands, the per-cog handling section presents a prefixed command. The main difference you may notice is the change in command signature.

To make the distinguishing features apparent:

  • Slash commands use on_application_command_error(ctx: discord.ApplicationContext, error: discord.DiscordException) as presented in the per-command and global handling examples.
  • Prefixed commands use on_command_error(ctx: commands.Context, error: commands.CommandError) as presented in the per-cog handling example.

This distinction is made as both types of commands need their own system for handling errors that are specific to themselves.