Docs academy · Guide 3 of 8 · 12 min read
Your first bot in discord.py
Build a working bot with a slash command in Python, using discord.py 2.x.
By the end of this guide you'll have a bot online that answers a /ping command. You need the application, token and test server from Start here.
Set up the project
Install a current version of Python, then create a project with a virtual environment, which keeps its packages separate from the rest of your computer.
On macOS or Linux:
mkdir my-bot
cd my-bot
python3 -m venv .venv
source .venv/bin/activate
pip install discord.py python-dotenv
On Windows, the activate line is different:
.venv\Scripts\activate
Create a .env file in the same folder. DISCORD_GUILD_ID is your test server's ID. To get it, turn on Developer Mode in Discord's advanced settings, then right-click your server and click Copy Server ID.
DISCORD_TOKEN=paste-your-token-here
DISCORD_GUILD_ID=your-test-server-id
Add .env and .venv/ to a .gitignore file now, before you forget.
Write the bot
Save this as bot.py:
import os
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv()
TEST_GUILD = discord.Object(id=int(os.environ["DISCORD_GUILD_ID"]))
class MyBot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
# Registers the commands with Discord. In your test server this is instant.
self.tree.copy_global_to(guild=TEST_GUILD)
await self.tree.sync(guild=TEST_GUILD)
bot = MyBot()
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
@bot.tree.command(description="Check that the bot is alive")
async def ping(interaction: discord.Interaction):
latency = round(bot.latency * 1000)
await interaction.response.send_message(f"Pong! Gateway latency: {latency} ms")
bot.run(os.environ["DISCORD_TOKEN"])
Start it:
python bot.py
When you see Logged in as, your bot is online. Go to your test server and type /ping.
What just happened
Intentstell Discord which events to send you.Intents.default()is everything except the privileged ones. Guide 4 explains those.CommandTreeholds your slash commands.setup_hookruns once while the bot starts up, which is the right place to sync commands. Don't sync inon_ready, because that can fire many times.interaction.response.send_messageanswers the command. Discord expects a reply within 3 seconds.
If your command needs longer than 3 seconds, say so first, then answer when you're done:
await interaction.response.defer()
result = await do_slow_work()
await interaction.followup.send(result)
Keep slow work asynchronous. A blocking call such as time.sleep or requests.get freezes the whole bot, including its connection to Discord. Use asyncio.sleep and an async library such as aiohttp instead.
Options on a command
Function parameters become command options, and type hints tell Discord what to accept:
@bot.tree.command(description="Say hello to someone")
@app_commands.describe(member="Who to greet")
async def hello(interaction: discord.Interaction, member: discord.Member):
await interaction.response.send_message(f"Hello, {member.mention}!")
Restart your bot and setup_hook syncs the new command for you.
Don't let errors kill your bot
Handle failures in one place and tell the user what happened:
@bot.tree.error
async def on_app_command_error(interaction: discord.Interaction, error: app_commands.AppCommandError):
print(f"Command failed: {error}")
message = "Something went wrong running that command."
if interaction.response.is_done():
await interaction.followup.send(message, ephemeral=True)
else:
await interaction.response.send_message(message, ephemeral=True)
Common problems
| What you see | What it usually means |
| --- | --- |
| LoginFailure: Improper token | The token is wrong, or .env isn't being read. |
| KeyError: 'DISCORD_TOKEN' | .env is missing or in a different folder. |
| /ping doesn't appear | The invite link was missing applications.commands, or DISCORD_GUILD_ID points at another server. |
| "The application did not respond" | Your bot isn't running, or it took more than 3 seconds without deferring. |
| PrivilegedIntentsRequired | You requested a privileged intent that isn't switched on in the Developer Portal. |
Next
Your bot works, but its commands only exist in your test server. Continue with Slash commands, intents and permissions.