NewSchokr 13 is here. A whole new UI, seven locations, snapshots and more. Read more →

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

CallEffect
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.commands scope. 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".