How to add slash commands in discord.py (app_commands and tree.sync)
Create slash commands with discord.py 2.x app_commands, sync them to one server or globally with tree.sync, and fix commands that don't show up.
Updated · 1 min read
discord.py 2.x supports slash commands through discord.app_commands. You add commands to the bot's command tree (bot.tree), then call tree.sync() to send them to Discord. Commands don't appear until they've been synced.
A complete example
import os
import discord
from discord import app_commands
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
TEST_GUILD_ID = os.getenv("GUILD_ID") # set while testing, remove for global
class MyBot(commands.Bot):
def __init__(self):
super().__init__(command_prefix="!", intents=discord.Intents.default())
async def setup_hook(self):
if TEST_GUILD_ID:
guild = discord.Object(id=int(TEST_GUILD_ID))
self.tree.copy_global_to(guild=guild)
synced = await self.tree.sync(guild=guild)
else:
synced = await self.tree.sync()
print(f"Synced {len(synced)} commands")
bot = MyBot()
@bot.tree.command(name="ping", description="Check that the bot is alive")
async def ping(interaction: discord.Interaction):
await interaction.response.send_message(f"Pong! {round(bot.latency * 1000)} ms")
@bot.tree.command(name="echo", description="Repeat a message")
@app_commands.describe(text="What to repeat")
async def echo(interaction: discord.Interaction, text: str):
await interaction.response.send_message(text)
bot.run(os.environ["DISCORD_TOKEN"])
Slash commands don't need the message_content intent, so Intents.default() is enough here.
Global vs guild sync
| Call | Effect |
|---|---|
await bot.tree.sync() | Registers global commands for every server. Can take longer to appear. |
await bot.tree.sync(guild=guild) | Registers commands for one server. Appears immediately. |
bot.tree.copy_global_to(guild=guild) | Copies your global commands into that guild's set before a guild sync. |
Use a guild sync while developing and switch to a global sync when you're happy. To get a server ID, enable Developer Mode in Discord's Advanced settings and right-click the server.
Where to sync (and where not to)
Sync in setup_hook, which runs once before the bot connects. Avoid syncing in on_ready: it can fire again after reconnects, and the sync endpoint is rate limited. Many developers go further and sync only from an owner-only prefix command, so they control exactly when it happens.
Commands in cogs
Inside a cog, use @app_commands.command() on methods:
import random
import discord
from discord import app_commands
from discord.ext import commands
class Fun(commands.Cog):
@app_commands.command(name="roll", description="Roll a die")
async def roll(self, interaction: discord.Interaction, sides: int = 6):
await interaction.response.send_message(str(random.randint(1, sides)))
async def setup(bot):
await bot.add_cog(Fun())
Load cogs in setup_hook with await self.load_extension("cogs.fun") before you sync, so their commands are included.
Removing duplicates
If you synced the same commands globally and to a guild, they show twice in that server. Clear the guild copy:
bot.tree.clear_commands(guild=guild)
await bot.tree.sync(guild=guild)
Long-running commands
You must respond within 3 seconds. If the command does slow work, defer first and follow up:
await interaction.response.defer()
result = await do_slow_work()
await interaction.followup.send(result)
Commands don't show up?
- The bot was invited without the
applications.commandsscope. See inviting your bot. - The sync never ran, or ran before cogs were loaded. Check the "Synced N commands" line in the console.
- Restart your Discord client (Ctrl+R) to refresh its command cache.
- For "The application did not respond", see fixing "This interaction failed".