For discord.py 2.x. Two words that get used loosely: a cog is a class that groups commands and listeners; an extension is a Python file that discord.py can load, which usually adds one cog.
The layout
my-bot/
bot.py
requirements.txt
cogs/
__init__.py (empty)
fun.py
moderation.pyA cog
# cogs/fun.py
import random
import discord
from discord import app_commands
from discord.ext import commands
class Fun(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@app_commands.command(description="Roll a die")
async def roll(self, interaction: discord.Interaction, sides: int = 6):
await interaction.response.send_message(f"You rolled {random.randint(1, sides)}")
@commands.command()
async def hello(self, ctx: commands.Context):
await ctx.send(f"Hello {ctx.author.mention}")
@commands.Cog.listener()
async def on_member_join(self, member: discord.Member):
channel = member.guild.system_channel
if channel:
await channel.send(f"Welcome, {member.mention}!")
async def setup(bot: commands.Bot):
await bot.add_cog(Fun(bot))- Every command and listener takes
selffirst. Forgetting it gives confusing errors about the wrong number of arguments. - Slash commands in a cog use
@app_commands.command, not@bot.tree.command. Prefix commands use@commands.command. - Events use
@commands.Cog.listener(), and the method name is the event name. - The
setupfunction at the bottom is what makes the file an extension. It must beasyncin discord.py 2. on_member_joinneeds the Server Members intent in your code and in the Developer Portal. The welcome messages guide has the rest, goodbyes included.
Loading them
# bot.py
import os
import discord
from discord.ext import commands
EXTENSIONS = ["cogs.fun"] # one entry per file in cogs/; add "cogs.moderation" once that file exists
class MyBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True # for prefix commands
intents.members = True # for on_member_join
super().__init__(command_prefix="!", intents=intents)
async def setup_hook(self):
for ext in EXTENSIONS:
await self.load_extension(ext)
await self.tree.sync() # after loading, or the cogs' slash commands are missing
bot = MyBot()
bot.run(os.environ["BOT_TOKEN"])The order matters. tree.sync() uploads whatever commands exist at that moment. Sync before loading the extensions and Discord gets a list without them.
Extension names are module paths with dots (cogs.fun), not file paths (cogs/fun.py). Python looks them up from the folder bot.py is in, so they load whichever folder you start the bot from. A name in the list with no file behind it stops the bot at startup with ExtensionNotFound. File paths you open yourself are different: open("data.json") is relative to the folder the bot was started in.
Loading every file in the folder
import pathlib
async def setup_hook(self):
for path in sorted(pathlib.Path(__file__).parent.joinpath("cogs").glob("*.py")):
if path.stem != "__init__":
await self.load_extension(f"cogs.{path.stem}")
await self.tree.sync()Reloading without restarting
An owner-only command that reloads a cog after you edit it:
@commands.command()
@commands.is_owner()
async def reload(self, ctx: commands.Context, name: str):
await self.bot.reload_extension(f"cogs.{name}")
await ctx.send(f"Reloaded {name}")Reloading picks up changed Python code. It does not re-sync slash commands: if you added, renamed or removed one, run a sync as well (see slash commands in discord.py). If the bot's hosting pulls from Git on restart, a plain restart does both.
Shared things: a database, an HTTP session
Create shared objects once on the bot, and reach them from any cog through self.bot:
import aiohttp
class MyBot(commands.Bot):
async def setup_hook(self):
self.http_session = aiohttp.ClientSession()
for ext in EXTENSIONS:
await self.load_extension(ext)
await self.tree.sync()
async def close(self):
await self.http_session.close()
await super().close()Then in a cog: async with self.bot.http_session.get(url) as r: .... Opening a new session for every command leaks connections and eventually memory.
Errors you will meet
ExtensionNotFound: Extension 'cogs.fun' could not be loaded or found.: there is nocogs/fun.pynext tobot.py, or the name in the list is spelt differently from the file. Linux, where most bots run, treatsFun.pyandfun.pyas different files.NoEntryPointError: the file has nosetupfunction, or it is notasync.ExtensionAlreadyLoaded:load_extensionran twice, often because it was put inon_ready, which can fire more than once. Usesetup_hook.- RuntimeWarning: coroutine 'Bot.add_cog' was never awaited: old discord.py 1.x code. In 2.x both
add_cogandload_extensionneedawait. - Slash commands from a cog missing: synced before loading, or the cog raised an error while loading that was scrolled past. Read the console from the top of the start.
Did this page help?
Thanks for the feedback.