Discord bot TypeError: function missing required parameter 'ctx'

I’m working on a Discord bot and keep getting a TypeError about a missing ‘ctx’ parameter. Here’s my current code:

import discord
import os
from dotenv import load_dotenv
from discord.ext import commands
import logging


class BotHandler:
    def __init__(self) -> None:
        self.permissions = discord.Intents.all()
        self.permissions.messages = True
        self.permissions.message_content = True
        self.permissions.guilds = True
        self.permissions.members = True
        self.permissions.presences = True
        self.client = commands.Bot(command_prefix='!', intents=self.permissions)

    async def on_ready(self):
        print("Bot is online.")
    
    @commands.command()
    async def greet(self, context):
        await context.channel.send('Hi there!')
        print("GREETING SENT!")


if __name__ == '__main__':
    load_dotenv()

    BOT_TOKEN = os.getenv('BOT_TOKEN')

    logging.basicConfig(level=logging.DEBUG)
    my_bot = BotHandler()
    my_bot.client.add_command(my_bot.greet)

    my_bot.client.run(BOT_TOKEN)

The error traceback shows:

ERROR:discord.ext.commands.bot:Ignoring exception in command greet
Traceback (most recent call last):
  File "/lib/python3.12/site-packages/discord/ext/commands/core.py", line 235, in wrapped
    ret = await coro(*args, **kwargs)
  TypeError: BotHandler.greet() missing 1 required positional argument: 'context'

What’s causing this issue and how can I resolve it?

The problem is how you’re registering the command. When you use my_bot.client.add_command(my_bot.greet), discord.py doesn’t know the command belongs to your BotHandler instance, so it can’t pass the self parameter automatically. I hit this same issue when I started using custom classes for bots. Easiest fix? Make your BotHandler inherit from commands.Bot instead of wrapping it:

class BotHandler(commands.Bot):
    def __init__(self) -> None:
        permissions = discord.Intents.all()
        permissions.messages = True
        permissions.message_content = True
        super().__init__(command_prefix='!', intents=permissions)
    
    @commands.command()
    async def greet(self, ctx):
        await ctx.send('Hi there!')
        print("GREETING SENT!")

Then just create and run it: my_bot = BotHandler() and my_bot.run(BOT_TOKEN). Now the command decorator binds properly to your class and discord.py handles the context parameter like it should.

You could also use functools.partial to bind the self parameter when adding the command. Just do from functools import partial then my_bot.client.add_command(commands.Command(my_bot.greet, name='greet')) but Jack’s inheritance approach is way cleaner and less hacky