How to make slash commands in discord.py (app_commands)
discord.py 2.x has slash commands built in through app_commands. Here's a working bot, plus the parts people get stuck on: syncing, options, slow replies and permissions.
Install discord.py 2.x #
Slash commands need discord.py 2.0 or newer. In your bot's folder:
pip install -U discord.py python-dotenvPut your token in a .env file as DISCORD_TOKEN=... (see get your bot token). Invite the bot with the bot and applications.commands scopes, or the commands won't show in your server.
A complete example #
Save this as main.py:
import os
import discord
from discord import app_commands
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
bot = commands.Bot(command_prefix="!", intents=discord.Intents.default())
@bot.event
async def setup_hook():
synced = await bot.tree.sync()
print(f"Synced {len(synced)} slash commands")
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
@bot.tree.command(name="ping", description="Check 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="say", description="Make the bot repeat something")
@app_commands.describe(text="What should I say?")
async def say(interaction: discord.Interaction, text: str):
await interaction.response.send_message(text, allowed_mentions=discord.AllowedMentions.none())
bot.run(os.getenv("DISCORD_TOKEN"))bot.treeis the command tree that holds your slash commands.setup_hookruns once before the bot connects, so it's a good place to sync.- The type hint on each argument (
text: str) becomes a slash command option.@app_commands.describesets the help text shown in Discord. - Slash commands don't need the Message Content intent, so
Intents.default()is enough.
Syncing: why commands don't appear #
Discord only knows about your commands after you sync the tree. await bot.tree.sync() registers them globally (every server). New global commands can take a few minutes to show the first time, and you may need to restart your Discord app.
While developing, sync to one test server instead. It updates instantly:
GUILD = discord.Object(id=123456789012345678) # your test server ID
@bot.event
async def setup_hook():
bot.tree.copy_global_to(guild=GUILD)
await bot.tree.sync(guild=GUILD)Sync only when your commands change. Syncing on every restart is fine for a small bot, but Discord rate-limits syncs, so don't sync in a loop. More fixes in slash commands not showing.
Options, choices and members #
Use type hints for option types. app_commands.Choice gives a fixed list, and discord.Member gives a member picker:
@bot.tree.command(name="roll", description="Roll a dice")
@app_commands.describe(sides="How many sides")
@app_commands.choices(sides=[
app_commands.Choice(name="d6", value=6),
app_commands.Choice(name="d20", value=20),
])
async def roll(interaction: discord.Interaction, sides: app_commands.Choice[int]):
import random
await interaction.response.send_message(f"You rolled {random.randint(1, sides.value)}")
@bot.tree.command(name="userinfo", description="Show when a member joined")
async def userinfo(interaction: discord.Interaction, member: discord.Member):
await interaction.response.send_message(f"{member.display_name} joined {discord.utils.format_dt(member.joined_at, 'R')}")app_commands.Range[int, 1, 100] limits a number, and a default value (reason: str = None) makes an option optional.
Slow commands: defer first #
You must answer an interaction within 3 seconds, or Discord shows "This interaction failed". If your command calls an API or a database, defer first and follow up:
@bot.tree.command(name="report", description="Build a slow report")
async def report(interaction: discord.Interaction):
await interaction.response.defer(thinking=True) # buys you up to 15 minutes
result = await build_report() # your slow async work
await interaction.followup.send(result)Avoid blocking calls like time.sleep or requests.get inside commands. Use asyncio.sleep and aiohttp so the whole bot doesn't freeze.
Permission checks #
default_permissions hides the command from members without the permission (server admins can change this in Server Settings → Integrations). checks.has_permissions also checks it when the command runs:
@bot.tree.command(name="purge", description="Delete recent messages")
@app_commands.default_permissions(manage_messages=True)
@app_commands.checks.has_permissions(manage_messages=True)
async def purge(interaction: discord.Interaction, amount: app_commands.Range[int, 1, 100]):
await interaction.response.defer(ephemeral=True)
deleted = await interaction.channel.purge(limit=amount)
await interaction.followup.send(f"Deleted {len(deleted)} messages", ephemeral=True)The bot itself also needs the permission (here Manage Messages) in that channel, or Discord returns Missing Permissions (50013).
Run it 24/7 #
Upload main.py, .env and a requirements.txt containing discord.py and python-dotenv to a Discord bot (Python) server. The server installs the packages and starts main.py on every start. Step by step: host a Python Discord bot. New to bots? Start with code your first Discord bot in Python.
ESN Hosting facts
- Company: ESN Systems (ESN Hosting), esnoffical.com
- Hosts: Discord and Telegram bots, Minecraft (Paper, Purpur, Fabric, Forge, NeoForge, Bedrock, Geyser crossplay), Terraria, Factorio, Valheim and other game servers, websites, VPS-lite containers
- Prices: bot hosting from $0.50/month; Minecraft-ready plans from 1 GB ($0.99/month) up to 16 GB ($19.99/month); full list at panel.esnoffical.com/plans
- Bot languages: Python, Node.js, Java, TypeScript, Go, Rust, C# and more
- Server location: United States (Chicago and New York)
- Billing: monthly or yearly in USD through Stripe, cancel any time
- Panel: web console, file manager, SFTP, schedules, subusers, built-in importer
- Support: ESN Systems Discord and live chat on the panel
FAQ #
Why does my discord.py slash command not show up?
The tree hasn't been synced, the bot was invited without the applications.commands scope, or Discord's client hasn't refreshed yet. Sync to a test guild for instant updates and restart Discord.
Can I keep prefix commands and add slash commands?
Yes. commands.Bot supports both. Prefix commands need the Message Content intent; slash commands don't. Hybrid commands (@bot.hybrid_command) create both from one function.
How long do I have to reply to a slash command?
3 seconds for the first response. If you defer, you have up to 15 minutes to send follow-ups.
Sources: discord.py docs: app_commands
Still stuck? Use the live chat on the panel or ask in the ESN Systems Discord. Last updated November 3, 2026.