Skip to content

Checks⚓︎

Checks are utility decorators that are called before to the execution of commands.

These checks should be predicates that take in a single parameter taking a Context. If the check returns a False-like value then during invocation a CheckFailure exception is raised.

If an exception should be thrown in the predicate then it should be a subclass of CommandError. Any exception not subclassed from it will be propagated.

Usage⚓︎

Check is only a function that, based on the input, either throws an error or returns True/False.

Per-command apply⚓︎

Adding check to any single command

commands.check⚓︎

A decorator that adds a single check to the prefix command

def some_single_check(ctx: commands.Context):
    ...

@bot.command()
@commands.check(some_single_check)
async def foo(ctx: commands.Context):
    await ctx.send('You passed the check!')

commands.check_any⚓︎

A check() that is added that checks if any of the checks passed will pass, i.e. using logical OR.

def first_check(ctx: commands.Context):
    ...

def second_check(ctx: commands.Context):
    ...

@bot.command()
@commands.check_any(first_check, second_check)
async def foo(ctx: commands.Context):
    await ctx.send('You passed at least one check!')

app_commands.check⚓︎

A decorator that adds a single check to the slash command

def some_single_check(interaction: discord.Interaction):
    ...

@bot.command()
@app_commands.check(some_single_check)
async def foo(interaction: discord.Interaction):
    await ctx.send('You passed the check!')

Global apply⚓︎

Adding check to all existing prefix commands

To add a kind of global check for slash commands you can override CommandTree.interaction_check

bot.check⚓︎

A decorator that adds a global check to the bot.

A global check is similar to a check() that is applied on a per command basis except it is run before any command checks have been verified and applies to every command the bot has.

@bot.check
def check(ctx: commands.Context):
    ...

bot.check_once⚓︎

Same as bot.check except it is called only once per invoke() call

Regular global checks are called whenever a command is called or Command.can_run() is called. This type of check bypasses that and ensures that it’s called only once, even inside the default help command.

@bot.check_once
def check(ctx: commands.Context):
    ...

bot.before_invoke⚓︎

A decorator that registers a coroutine as a pre-invoke hook.

@bot.before_invoke
async def handler(ctx: commands.Context):
    print(f"Command '{ctx.command.name}' is started")

Note

The bot.before_invoke and bot.after_invoke hooks are only called if all checks and argument parsing procedures pass without error. If any check or argument parsing procedures fail then the hooks are not called.

bot.after_invoke⚓︎

A decorator that registers a coroutine as a post-invoke hook.

@bot.after_invoke
async def handler(ctx: commands.Context):
    print(f"Command '{ctx.command.name}' is finished")

Per-cog apply⚓︎

Adding a check on each command inside the cog

cog_check⚓︎

A special method that is registered as a commands.check() for every prefix command and subcommand in this cog.

class MyCog(commands.Cog):
    async def cog_check(self, ctx: commands.Context):
        ...

    @commands.command()
    async def foo(self, ctx: commands.Context):
        ...

interaction_check⚓︎

A special method that is registered as a app_commands.check() for every slash command and subcommand in this cog.

class MyCog(commands.Cog):
    async def interaction_check(self, interaction: discord.Interaction):
        ...

    @app_commands.command()
    async def foo(self, interaction: discord.Interaction):
        ...

Handling check failures⚓︎

When an error inside check happens, the error is propagated to the error handlers.

If you don't raise an exception but return false-like value, then it will get wrapped up into a CheckFailure exception.

Tip

Check out Error Handling page for more examples and explanations about error handling

This is an example of how you can handle check failure for a single command

class CustomException(commands.CommandError): ...

async def check(ctx: commands.Context):
    if "1" in ctx.message.content:
        raise CustomException()
    if "2" in ctx.message.content:
        return False
    return True

@commands.check(check)
@bot.command()
async def foo(ctx: commands.Context):
    await ctx.send("Success!")

@foo.error
async def handler(ctx: commands.Context, error: commands.CommandError):
    if isinstance(error, CustomException):
        await ctx.send("CustomException was raised inside check!")
    elif isinstance(error, commands.CheckFailure):
        await ctx.send("Check has failed!")
    else:
        await ctx.send(f"Got unexpected error: {error}")

Showcase

Note

This error handler is used here for further demonstration

@foo.error
async def handler(ctx: commands.Context, error: commands.CommandError):
    await ctx.send(f"{error.__class__.__name__} | {error}")

Built-in checks⚓︎

You may view all of discord.py's relevant checks in the documentation.

Roles⚓︎

has_role⚓︎

Checks if the member invoking the command has the role specified via the name or ID specified.

@bot.command()
@commands.has_role("x")
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
@bot.command()
@commands.has_role(1124650487942225970)
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
@bot.tree.command()
@app_commands.checks.has_role("x")
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")
@bot.tree.command()
@app_commands.checks.has_role(1124650487942225970)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")
@bot.hybrid_command()
@commands.has_role("x")
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
@bot.hybrid_command()
@commands.has_role(1124650487942225970)
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")

DM

Without Role

With Role

has_any_role⚓︎

Similar to has_role, but takes unspecified amount of argument and returns True if the member invoking the command has any of the roles specified

bot_has_role⚓︎

Similar to has_role except checks if the bot itself has the role.

bot_has_any_role⚓︎

Similar to has_any_role except checks if the bot itself has the role.

Permissions⚓︎

List of existing permissions

They are attributes of discord.Permissions

add_reactions, administrator, attach_files, ban_members, change_nickname, connect, create_instant_invite, create_private_threads, create_public_threads, deafen_members, embed_links, external_emojis, external_stickers, kick_members, manage_channels, manage_emojis, manage_emojis_and_stickers, manage_events, manage_guild, manage_messages, manage_nicknames, manage_permissions, manage_roles, manage_threads, manage_webhooks, mention_everyone, moderate_members, move_members, mute_members, priority_speaker, read_message_history, read_messages, request_to_speak, send_messages, send_messages_in_threads, send_tts_messages, speak, stream, use_application_commands, use_embedded_activities, use_external_emojis, use_external_stickers, use_voice_activation, view_audit_log, view_channel, view_guild_insights

has_permissions⚓︎

Checks if the member has all of the permissions necessary.

@bot.command()
@commands.has_permissions(manage_messages=True)
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
@bot.tree.command()
@app_commands.checks.has_permissions(manage_messages=True)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")
@bot.hybrid_command()
@commands.has_permissions(manage_messages=True)
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")

Note

This check operates on the current channel permissions, not the guild wide permissions

Without Permissions

With Permissions

has_guild_permissions⚓︎

Similar to has_permissions, but operates on guild wide permissions instead of the current channel permissions.

bot_has_permissions⚓︎

Similar to has_permissions except checks if the bot itself has the permissions listed.

bot_has_guild_permissions⚓︎

Similar to has_guild_permissions except checks if the bot itself has the permissions listed.

Channel⚓︎

dm_only⚓︎

Checks if command is invoked inside a DM

@bot.command()
@commands.dm_only()
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
  • There is no such check for application commands built-in.
def dm_only(interaction):
    return interaction.guild is None

@bot.tree.command()
@app_commands.check(dm_only)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")
@bot.hybrid_command()
@commands.dm_only()
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")

guild_only⚓︎

Checks if command is invoked inside a guild

@bot.command()
@commands.guild_only()
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
  • There is no such check for application commands built-in.
def guild_only(interaction):
    return interaction.guild is not None

@bot.tree.command()
@app_commands.check(guild_only)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")

Tip

You can use app_commands.guild_only() instead of check

@bot.tree.command()
@discord.app_commands.guild_only()
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")

With it everything will be handled by discord itself

@bot.hybrid_command()
@commands.guild_only()
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")

is_nsfw⚓︎

Checks if the channel is a NSFW channel.

@bot.command()
@commands.is_nsfw()
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
  • There is no such check for application commands built-in.
def is_nsfw(interaction):
    return interaction.channel.is_nsfw()

@bot.tree.command()
@app_commands.check(is_nsfw)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")

Tip

You can use nsfw=True argument instead of check

@bot.tree.command(nsfw=True)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")

With it everything will be handled by discord itself

@bot.hybrid_command()
@commands.is_nsfw()
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")

Person⚓︎

is_owner⚓︎

Checks if the person invoking this command is the owner of the bot.

This is powered by Bot.is_owner().

@bot.command()
@commands.is_owner()
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")
  • There is no such check for application commands built-in.
async def is_owner(interaction):
    return await bot.is_owner(interaction.user)

@bot.tree.command()
@app_commands.check(is_owner)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message(f"Success!")
@bot.hybrid_command()
@commands.is_owner()  
async def foo(ctx: commands.Context):
    await ctx.send(f"Success!")

Hooks⚓︎

before_invoke⚓︎

Registers a coroutine as a pre-invoke hook.

async def func(ctx: commands.Context):
    await ctx.send("hook")


@bot.command()
@commands.before_invoke(func)
async def foo(ctx: commands.Context):
    await ctx.send("command")  
async def func(interaction: discord.Interaction):
    await interaction.channel.send("hook")


@bot.tree.command()
@commands.before_invoke(func)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message("command")  
async def func(ctx: commands.Context):
    await ctx.send("hook")


@bot.hybrid_command()
@commands.before_invoke(func)
async def foo(ctx: commands.Context):
    await ctx.send("command")  

Showcase

after_invoke⚓︎

Registers a coroutine as a post-invoke hook.

async def func(ctx: commands.Context):
    await ctx.send("hook")


@bot.command()
@commands.after_invoke(func)
async def foo(ctx: commands.Context):
    await ctx.send("command")  
async def func(interaction: discord.Interaction):
    await interaction.channel.send("hook")


@bot.tree.command()
@commands.after_invoke(func)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message("command")  
async def func(ctx: commands.Context):
    await ctx.send("hook")


@bot.hybrid_command()
@commands.after_invoke(func)
async def foo(ctx: commands.Context):
    await ctx.send("command")  

Showcase

Cooldowns⚓︎

A cooldown allows a command to only be used a specific amount of times in a specific time frame. These cooldowns can be based either on a per-guild, per-channel, per-user, per-role or global basis. Denoted by the third argument of type which must be of enum type BucketType.

cooldown⚓︎

Adds a cooldown to a Command

@bot.command()
@commands.cooldown(1, 10, commands.BucketType.user)
async def foo(ctx: commands.Context):
    await ctx.send("Success!")
@bot.tree.command()
@app_commands.checks.cooldown(1, 10, key=lambda i: (i.user.id,))
async def foo(ctx: commands.Context):
    await ctx.send("Success!")
@bot.hybrid_command()
@commands.cooldown(1, 10, commands.BucketType.user)
async def foo(ctx: commands.Context):
    await ctx.send("Success!")

Showcase

dynamic_cooldown⚓︎

Adds a dynamic cooldown to a Command

This differs from cooldown in that it takes a function that accepts a single parameter of type Context (Interaction for slash) and must return a Cooldown or None. If None is returned then that cooldown is effectively bypassed.

def cooldown(ctx: commands.Context):
    """A cooldown for 10 seconds for everyone except listed users"""
    if ctx.author.id in (656919778572632094, 703327554936766554):
        return
    return commands.Cooldown(1, 10)


@bot.command()
@commands.dynamic_cooldown(cooldown, commands.BucketType.user)
async def foo(ctx: commands.Context):
    await ctx.send("Success!")
def cooldown(interaction: discord.Interaction):
    """A cooldown for 10 seconds for everyone except listed users"""
    if interaction.author.id in (656919778572632094, 703327554936766554):
        return
    return app_commands.Cooldown(1, 10)


@bot.tree.command()
@app_commands.checks.dynamic_cooldown(cooldown, commands.BucketType.user)
async def foo(interaction: discord.Interaction):
    await interaction.response.send_message("Success!")
def cooldown(ctx: commands.Context):
    """A cooldown for 10 seconds for everyone except listed users"""
    if ctx.author.id in (656919778572632094, 703327554936766554):
        return
    return commands.Cooldown(1, 10)


@bot.hybrid_command()
@commands.dynamic_cooldown(cooldown, commands.BucketType.user)
async def foo(ctx: commands.Context):
    await ctx.send("Success!")

Showcase

max_concurrency⚓︎

Adds a maximum concurrency to a Command

This enables you to only allow a certain number of command invocations at the same time, for example if a command takes too long or if only one user can use it at a time. This differs from a cooldown in that there is no set waiting period or token bucket – only a set number of people can run the command.

@bot.command()
@commands.max_concurrency(1, commands.BucketType.member, wait=False)
async def foo(ctx: commands.Context):
    await asyncio.sleep(1)
    await ctx.send("Success!")
@bot.tree.command()
@commands.max_concurrency(1, commands.BucketType.member, wait=False)
async def foo(interaction: discord.Interaction):
    await asyncio.sleep(1)
    await interaction.response.send_message("Success!")
@bot.hybrid_command()
@commands.max_concurrency(1, commands.BucketType.member, wait=False)
async def foo(ctx: commands.Context):
    await asyncio.sleep(1)
    await ctx.send("Success!")

Showcase

Custom Checks⚓︎

Creating a new check⚓︎

is_me⚓︎

Creating a basic check to see if the command invoker is you.

def check_if_it_is_me(ctx):
    return ctx.message.author.id == 85309593344815104


@bot.command()
@commands.check(check_if_it_is_me)
async def only_for_me(ctx):
    await ctx.send('I know you!')

safe_content⚓︎

Checks if there are no banned words in the command's message content

banwords = {"rabbit", "horse"}


async def safe_content(ctx):
    return not (set(ctx.message.content.lower().split()) & banwords)


@bot.command()
@commands.check(safe_content)
async def check_content(ctx):
    await ctx.send("Content is clean!")

Extending existing checks⚓︎

A special attribute named predicate is bound to the value returned by commands.check decorator to retrieve the predicate passed to the decorator.

def owner_or_permissions(**perms):
    original = commands.has_permissions(**perms).predicate

    async def extended_check(ctx):
        if ctx.guild is None:
            return False
        return ctx.guild.owner_id == ctx.author.id or await original(ctx)

    return commands.check(extended_check)

This will create a check that uses commands.has_permissions with our custom check together to determine whether the user is the guild owner or he has the required permissions.

Comments