Make a Discord bot in Python
From nothing to a bot that answers slash commands in your server, in about twenty minutes. You need Python installed and a Discord server you can add bots to.
10 minute read
1. Create the application
- Go to the Discord Developer Portal and press New Application. The name is what people will see.
- Open Bot. Press Reset Token and copy it. This is the bot's password: anyone with it can run your bot. Keep it somewhere private for now.
- On the same page, Privileged Gateway Intents can all stay off for this guide. You only need Message Content Intent if your bot will read ordinary messages, for example
!commands.
2. Invite it to your server
- Open OAuth2, then URL Generator.
- Tick
botandapplications.commands. - Under bot permissions, tick Send Messages (and whatever else your bot will need later).
- Open the URL at the bottom, pick your server, and authorise.
The bot shows up in the member list, offline. It comes online when your code runs.
3. Set up the project
You need Python 3.10 or newer (python --version). Make a folder and a virtual environment, so this bot's packages stay separate from everything else:
mkdir my-bot
cd my-bot
python -m venv venv
# Windows: venv\Scripts\activate
# macOS / Linux: source venv/bin/activate
pip install discord.py python-dotenv
pip freeze > requirements.txt
4. Keep the token out of the code
Create a file called .env in the folder:
BOT_TOKEN=paste-your-token-here
If you use Git, add .env and venv/ to .gitignore now, before your first commit. Tokens pushed to public repositories are found by scanners within minutes.
5. Write the bot
Create bot.py:
import os
import random
import discord
from discord import app_commands
from dotenv import load_dotenv
load_dotenv()
class MyBot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
# Tell Discord which slash commands exist. Runs once per start.
await self.tree.sync()
bot = MyBot()
@bot.event
async def on_ready():
print(f"Logged in as {bot.user}")
@bot.tree.command(description="Say hello")
async def hello(interaction: discord.Interaction):
await interaction.response.send_message(f"Hello, {interaction.user.mention}!")
@bot.tree.command(description="Roll a die")
@app_commands.describe(sides="How many sides the die has")
async def roll(interaction: discord.Interaction, sides: int = 6):
await interaction.response.send_message(f"You rolled {random.randint(1, sides)}")
bot.run(os.environ["BOT_TOKEN"])
6. Run it
python bot.py
You should see Logged in as and your bot's name, and the bot turns green in Discord. Type / in your server: /hello and /roll are listed under your bot. The first time, it can take a minute, and reloading Discord (Ctrl+R) helps.
If it did not work
KeyError: 'BOT_TOKEN': the.envfile is not in the folder you ran the command from, or the line is misspelt.LoginFailure: Improper token has been passed: you copied the Client Secret or the Application ID instead of the token, or reset the token afterwards. Reset it and copy it again.ModuleNotFoundError: No module named 'discord': the virtual environment is not active in this terminal. Activate it again.- Commands do not appear: the bot was invited without
applications.commands. Make a new invite link with both scopes and open it again. More in slash commands in discord.py.
Where to go next
- Commands that take time (calling an API, a database): defer the response first, or users see The application did not respond. The slash commands guide covers that, options and cogs.
- Keeping it online: the bot stops when you close the terminal or your computer sleeps. Your options for running it 24/7, free ones included.
- Before you share it: keeping the token safe.
When you want it running all the time, SnowServers runs exactly this kind of bot from $3 a month, with a 7-day free trial: upload the folder (without venv and .env) or push it to GitHub, paste the token into the panel, and it stays online. Hosting a discord.py bot walks through it.