Skip to content

Instantly share code, notes, and snippets.

@InterStella0
Last active December 17, 2025 09:13
Show Gist options
  • Select an option

  • Save InterStella0/b78488fb28cadf279dfd3164b9f0cf96 to your computer and use it in GitHub Desktop.

Select an option

Save InterStella0/b78488fb28cadf279dfd3164b9f0cf96 to your computer and use it in GitHub Desktop.
Walkthrough guide on subclassing HelpCommand

A basic guide on subclassing HelpCommand

This guide will walkthrough the ways to create a custom help command by subclassing HelpCommand.

Brief explanation of subclassing

Skip ahead if you already know this.

In simple terms, a subclass is a way to inherit a class behaviour/attributes from another class. Here's how you would subclass a class in Python.

class A:
    def __init__(self, attribute1):
        self.attribute1 = attribute1

    def method1(self):
        print("method 1")

    def method2(self):
        print("method 2")


class B(A):
    def __init__(self, attribute1, attribute2):
        super().__init__(attribute1) # This calls A().__init__ magic method.
        self.attribute2 = attribute2

    def method1(self): # Overrides A().method1
        print("Hi")

Given the example, the variable instance_a contains the instance of class A. As expected, the output will be this.

>>> instance_a = A(1)
>>> instance_a.attribute1
1
>>> instance_a.method1()
"method 1"

How about B class? instance_b contains the instance of class B, it inherits attributes/methods from class A. Meaning it will have everything that class A have, but with an additional attribute/methods.

>>> instance_b = B(1, 2)
>>> instance_b.attribute1
1
>>> instance_b.attribute2
2
>>> instance_b.method1()
"Hi"
>>> instance_b.method2()
"method 2"

Make sure to look and practice more into subclassing classes to understand fully on what it is before diving into subclassing HelpCommand.

Getting started

With that out of the way, let's get started. For subclassing HelpCommand, first, you would need to know the types of HelpCommand. Where each class has their own usage.

Types of HelpCommand class

There are a few types of HelpCommand classes that you can choose;

  1. DefaultHelpCommand a help command that is given by default.
  2. MinimalHelpCommand a slightly better help command.
  3. HelpCommand an empty class that is the base class for every HelpCommand you see. On its own, it will not do anything.

By default, help command is using the class DefaultHelpCommand. This is stored in bot.help_command. This attribute will ONLY accept instances that subclasses HelpCommand. Here is how you were to use the DefaultHelpCommand instance.

from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")
bot.help_command = commands.DefaultHelpCommand()

# OR

bot = commands.Bot(command_prefix="uwu ", help_command=commands.DefaultHelpCommand())
# Both are equivalent

Here's an example of what that looks like.

img.png Now, of course, this is done by default. I'm only showing you this as a demonstration. Don't scream at me

Let's do the same thing with MinimalHelpCommand next.

from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")
bot.help_command = commands.MinimalHelpCommand()

This is how that would look like:

minhelpcommand.png

Embed MinimalHelpCommand

Now say, you want the content to be inside an embed. But you don't want to change the content of DefaultHelpCommand/MinimalHelpCommand since you want a simple HelpCommand with minimal work. There is a short code from ?tag embed help example by gogert in discord.py server, a sample code you can follow shows this;

import discord
from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")

class MyNewHelp(commands.MinimalHelpCommand):
    async def send_pages(self):
        destination = self.get_destination()
        for page in self.paginator.pages:
            emby = discord.Embed(description=page)
            await destination.send(embed=emby)

bot.help_command = MyNewHelp()

The resulting code will show that it have the content of MinimalHelpCommand but in an embed.

embedminimalhelp.png

How does this work?

Looking over the MinimalHelpCommand source code, here. Every method that is responsible for <prefix>help <argument> will call MinimalHelpCommand.send_pages when it is about to send the content. This makes it easy to just override send_pages without having to override any other method there are in MinimalHelpCommand.

HelpCommand

Basic methods to override

If you want to use HelpCommand class, we need to understand the basic of subclassing HelpCommand. Here are a list of HelpCommand relevant methods, and it's responsibility.

  1. HelpCommand.send_bot_help(mapping) Gets called with <prefix>help
  2. HelpCommand.send_command_help(command) Gets called with <prefix>help <command>
  3. HelpCommand.send_group_help(group) Gets called with <prefix>help <group>
  4. HelpCommand.send_cog_help(cog) Gets called with <prefix>help <cog>

Useful attributes

  1. HelpCommand.context the Context object in the help command.
  2. HelpCommand.clean_prefix a cleanup prefix version that remove any mentions.

Seems simple enough? Now let's see what happens if you override one of the methods. Here's an example code of how you would do that. This override will say "hello!" when you type <prefix>help to demonstrate on what's going on.

We'll use HelpCommand.get_destination() to get the abc.Messageable instance for sending a message to the correct channel.

Code Example

from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")

class MyHelp(commands.HelpCommand):
    async def send_bot_help(self, mapping):
        channel = self.get_destination()
        await channel.send("hello!")

bot.help_command = MyHelp()

Output

hellohelpcommand.png

Keep in mind, using HelpCommand class will require overriding every send_x_help methods. For example, <prefix>help jsk is a command that should call send_command_help method. However, since HelpCommand is an empty class, it will not say anything.

help command

Let's work our way to create a <prefix> help. Given the documentation, await send_bot_help(mapping) method receives mapping(Mapping[Optional[Cog], List[Command]]) as its parameter. await indicates that it should be an async function.

What does this mean?

  • Mapping[] is a collections.abc.Mapping, for simplicity’s sake, this usually refers to a dictionary since it's under collections.abc.Mapping.
  • Optional[Cog] is a Cog object that has a chance to be None.
  • List[Command] is a list of Command objects.
  • Mapping[Optional[Cog], List[Command]] means it's a map object with Optional[Cog] as it's key and List[Command] as its value.

All of these are typehints in the typing module. You can learn more about it here.

Now, for an example, we will use this mapping given in the parameter of send_bot_help. For each of the command, we'll use HelpCommand.get_command_signature(command) to get the command signature of a command in an str form.

Example Code

import discord
from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")

class MyHelp(commands.HelpCommand):
    async def send_bot_help(self, mapping):
        embed = discord.Embed(title="Help")
        for cog, commands in mapping.items():
           command_signatures = [self.get_command_signature(c) for c in commands]
           if command_signatures:
                cog_name = getattr(cog, "qualified_name", "No Category")
                embed.add_field(name=cog_name, value="\n".join(command_signatures), inline=False)

        channel = self.get_destination()
        await channel.send(embed=embed)

bot.help_command = MyHelp()

How does it work?

  1. Create an embed.
  2. Use dict.items() to get an iterable of (Cog, list[Command]).
  3. Each element in list[Command], we will call self.get_command_signature(command) to get the proper signature of the command.
  4. If the list is empty, meaning, no commands is available in the cog, we don't need to show it, hence if command_signatures:.
  5. cog has a chance to be None, this refers to No Category. We'll use getattr to avoid getting an error to get cog's name through Cog.qualified_name.
  6. Using str.join each command will be displayed on a separate line.
  7. Once all of this is finished, display it.

The result

samplehelp.png

I agree, this does not look pretty. But this demonstrate how to create a help command with minimal effort. The reason why it looks ugly, is that we're using HelpCommand.get_command_signature(command). Let's override that method to make it a little more readable.

We'll borrow codes from MinimalHelpCommand.get_command_signature. Optionally, we can subclass MinimalHelpCommand instead of copying codes. I'm doing this as a demonstration of overriding other methods.

We'll also use HelpCommand.filter_commands, this method will filter commands by removing any commands that the user cannot use. It is a handy method to use.

The Example

import discord
from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")

class MyHelp(commands.HelpCommand):
   def get_command_signature(self, command):
        return '%s%s %s' % (self.clean_prefix, command.qualified_name, command.signature)

    async def send_bot_help(self, mapping):
        embed = discord.Embed(title="Help")
        for cog, commands in mapping.items():
           filtered = await self.filter_commands(commands, sort=True)
           command_signatures = [self.get_command_signature(c) for c in filtered]
           if command_signatures:
                cog_name = getattr(cog, "qualified_name", "No Category")
                embed.add_field(name=cog_name, value="\n".join(command_signatures), inline=False)

        channel = self.get_destination()
        await channel.send(embed=embed)

bot.help_command = MyHelp()

The resulting output

betterhelpcommand.png

This looks more readable than the other one. While this should cover most of your needs, you may want to know more helpful attribute that is available on HelpCommand in the official documentation.

help [argument] command

Now that the hard part is done, let's take a look at <prefix>help [argument]. The method responsible for this is as follows;

  1. send_command_help
  2. send_cog_help
  3. send_group_help

As a demonstration, let's go for send_command_help this method receive a Command object. For this, it's simple, all you have show is the attribute of the command.

For example, this is your command code, your goal is you want to show the help,aliases and the signature.

Command Code

@bot.command(help="Shows all bot's command usage in the server on a sorted list.",
             aliases=["br", "brrrr", "botranks", "botpos", "botposition", "botpositions"])
async def botrank(ctx, bot: discord.Member):
   pass

Then it's simple, you can display each of the attribute by Command.help and Command.aliases.

For the signature, instead of using the previous get_command_signature, we're going to subclass MinimalHelpCommand.

Help Code

import discord
from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")

class MyHelp(commands.MinimalHelpCommand):
    async def send_command_help(self, command):
        embed = discord.Embed(title=self.get_command_signature(command))
        embed.add_field(name="Help", value=command.help)
        alias = command.aliases
        if alias:
            embed.add_field(name="Aliases", value=", ".join(alias), inline=False)

        channel = self.get_destination()
        await channel.send(embed=embed)

bot.help_command = MyHelp()

What you get

commandhelpcommand.png

As you can see, it is very easy to create <prefix>help [argument]. The class already handles the pain of checking whether the given argument is a command, a cog, or a group command. It's up to you on how you want to display it, whether it's through a plain message, an embed or even using discord.ext.menus.

Command Attributes

Let's say, someone is spamming your help command. For a normal command, all you have to do to combat this is using a cooldown decorator and slap that thing above the command declaration. Or, what about if you want an alias? Usually, you would put an aliases kwargs in the command decorator. However, HelpCommand is a bit special, It's a god damn class. You can't just put a decorator on it and expect it to work.

That is when HelpCommand.command_attrs come to the rescue. This attribute can be set during the HelpCommand declaration, or a direct attribute assignment. According to the documentation, it accepts exactly the same thing as a command decorator in a form of a dictionary.

For example, we want to rename the help command as "hell" instead of "help" for whatever reason. We also want to make an alias for "help" so users can call the command with "hell" and "help". Finally, we want to put a cooldown, because help command messages are big, and we don't want people to spam those. So, what would the code look like?

Example Code

from discord.ext import commands

attributes = {
   'name': "hell",
   'aliases': ["help", "helps"],
   'cooldown': commands.Cooldown(2, 5.0, commands.BucketType.user)
}

# During declaration
help_object = commands.MinimalHelpCommand(command_attrs=attributes)

# OR through attribute assignment
help_object = commands.MinimalHelpCommand()
help_object.command_attrs = attributes

bot = commands.Bot(command_prefix="uwu ", help_command=help_object)

How does it work?

  1. sets the name into "hell" is refers to here 'name': "hell".
  2. sets the aliases by passing the list of str to the aliases key, which refers to here 'aliases': ["help", "helps"].
  3. sets the cooldown through the "cooldown" key by passing in a Cooldown object. This object will make a cooldown with a rate of 2, per 5 with a bucket type BucketType.user, which in simple terms, for every discord.User, they can call the command twice, every 5 seconds.
  4. We're going to use MinimalHelpCommand as the HelpCommand object.

The result

cooldownhelp.png

As you can see, the name of the help command is now "hell", and you can also trigger the help command by "help". It will also raise an OnCommandCooldown error if it was triggered 3 times in 5 seconds due to our Cooldown object. Of course, I didn't show that in the result, but you can try it yourself. You should handle the error in an error handler when an OnCommandCooldown is raised.

Error handling for HelpCommand

Basic error message override

What happens when <prefix>help command fails to get a command/cog/group? Simple, HelpCommand.send_error_message will be called. HelpCommand will not call on_command_error when it can't find an existing command. It will also give you an str instead of an error instance.

import discord
from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")

class MyHelp(commands.HelpCommand):
    async def send_error_message(self, error):
        embed = discord.Embed(title="Error", value=error)
        channel = self.get_destination()
        await channel.send(embed=embed)

bot.help_command = MyHelp()

error is a string that will only contain the message, all you have to do is display the message.

The output:

errorhelp.png

How about a local error handler?

Indeed, we have it. HelpCommand.on_help_command_error, this method is responsible for handling any error just like any other local error handler.

Code

import discord
from discord.ext import commands
bot = commands.Bot(command_prefix="uwu ")

class MyHelp(commands.HelpCommand):
    async def send_bot_help(self, mapping):
        raise commands.BadArgument("Something broke")

    async def on_help_command_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            embed = discord.Embed(title="Error", description=str(error))
            await ctx.send(embed=embed)
        else:
            raise error

bot.help_command = MyHelp()

Rather basic, just raise an error that subclasses commands.CommandError such as commands.BadArgument. The error raised will cause on_help_command_error to be invoked. The code shown will catch this commands.BadArgument instance that is stored in error variable, and show the message.

Output:

errorhandler.png

To be fair, you should create a proper error handler through this official documentation. Here.

There is also a lovely example by Mysty on error handling in general. Here. This example shows how to properly create a global error handler and local error handler.

The end

I hope that reading this walkthrough will assist you and give a better understanding on how to subclass HelpCommand. All the example code given are to demonstrate the feature of HelpCommand and feel free to try it. There are lots of creative things you can do to create a HelpCommand.

Here is how my help command looks like as an inspiration.

myhelp.png

Here's my code if you want to take a look. my help command.

Now, of course, any question regarding HelpCommand should be asked in the discord.py server because I don't really check this gist as much, and because there is a lot of helpful discord.py helpers if you're nice enough to them. :D

@pikaninja
Copy link

you do it the same way as outside, when defining your discord.Embed set the colour kwarg

@laughingwater
Copy link

you do it the same way as outside, when defining your discord.Embed set the colour kwarg

Yeah I figured that out myself lmao I was being dumb, but thanks anyways!

@InterStella0
Copy link
Author

InterStella0 commented Jun 27, 2021

How can I make the arguments non-case sensitive?

The problem was solved above, just scroll up you can see my answer on tackling it.

@Nirlep5252
Copy link

genius

@pikaninja
Copy link

for all the people asking questions here read the end >:(
image

@chirag-droid
Copy link

chirag-droid commented Jul 18, 2021

Thanks Sir, really helpful guide.
And I came to know about this when I wrote my own help command from scratch, handling everything myself.

@spl1ce0
Copy link

spl1ce0 commented Aug 4, 2021

hey, how can i make a paginated help command?

@ChesterChowWOV
Copy link

Well

IT IS THE BEST HELP COMMAND TUTORIAL EVER!!!
I love this!
Thank you very much for making this tutorial! I made a pretty help command!

@doumeem
Copy link

doumeem commented Aug 15, 2021

Best Walkthrough Ever.

@MatchTheMatrix
Copy link

MatchTheMatrix commented Aug 27, 2021

hey, how can i make a paginated help command?

Did you find it out? I want to make a help command with selects of Discord-Components :/

@InterStella0
Copy link
Author

InterStella0 commented Sep 4, 2021

Pagination is added to the walkthrough as a result of you guys keeps bothering me about it. They are covered here.

@Nirlep5252
Copy link

👀

@eitozx
Copy link

eitozx commented Oct 4, 2021

Awesome work, Thanks for sharing.

@bsod2528
Copy link

bsod2528 commented Oct 7, 2021

thx u guys uwu

your welcome sir

@MarzaElise
Copy link

hey

@InterStella0
Copy link
Author

Seems like people are still asking me about paginator stuff which is really not about this walkthrough. If you want to learn more about paginator, you can refer to here.

It's a walkthrough i wrote about pagination using discord-ext-menus and View similar to this walkthrough except more in depth.

@ptekelly
Copy link

ptekelly commented Apr 18, 2022

Hello Stella
I love this walkthrough - not only helped me create neater help command but teaching me a lot on classes.

@sgtlaggy
Copy link

sgtlaggy commented Jul 3, 2022

A lot of the 2.0 links are broken, /master/ was removed from docs, just /latest/ exists now.

@InterStella0
Copy link
Author

Hyperlinks are updated now, I was waiting for v2 to be released but it seems kinda long to wait for it.

@spl1ce0
Copy link

spl1ce0 commented Aug 24, 2022

Image links seem to not be working for me :( am I the only one?

@jvrring
Copy link

jvrring commented Aug 26, 2022

Image links seem to not be working for me :( am I the only one?

Nope, your not.
Clicking on them gives a "Not Found" error.

@InterStella0
Copy link
Author

I've readded the images because I accidentally deleted them during purging on files.

@tookender
Copy link

maybe you should also explain why having a slash help command is useless

@InterStella0
Copy link
Author

That seems self-explanatory considering that Discord itself lists the commands for you.

@Motzumoto
Copy link

We miss you in the dpy server :(

@InterStella0
Copy link
Author

Appreciate the kind gesture but i've nuked all of my socials for the sake of my mental health. also this is literally out of topic of the gist

@Motzumoto
Copy link

Appreciate the kind gesture but i've nuked all of my socials for the sake of my mental health. also this is literally out of topic of the gist

Im glad to hear you're still around, and yeah it is. Be safe <3

@brutiv
Copy link

brutiv commented Aug 31, 2024

Can anyone tell me how to make this a hybrid command?

@InterStella0
Copy link
Author

@Shefki-Berisha It is as simple as defining a slash command and reusing Context.send_help

@bot.tree.command()
async def help(interaction: Interaction, command_or_group: Optional[str]):
    ctx = await bot.get_context(interaction)
    entity = command_or_group and (command_or_group,) or ()
    await ctx.send_help(*entity)

Note that this wont include slash commands into your help output. That would be a bit more annoying to implement, so I have made a lib that implements it in starlight-dpy

@brutiv
Copy link

brutiv commented Sep 6, 2024 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment