Docs › Discord bots › Course
Part 2: Hello world with a /ping slash command
Part 2 of the Python Discord bot course: install Python 3.12 and discord.py, keep your token in a .env file and write a bot with a /ping slash command.
- Part 1: Create the bot account
- Part 2: Hello world with a /ping command (you are here)
- Part 3: Real commands, embeds, buttons and cogs
- Part 4: Save data with SQLite
- Part 5: Keep it online 24/7 on ESN
Now the bot gets a brain. By the end of this part it comes online and answers /ping with its latency.
1. Install Python 3.12 #
Download Python 3.12 from python.org. On Windows, tick Add python.exe to PATH in the installer. Then open a terminal (Command Prompt, PowerShell or Terminal) and check:
python --versionIt should print Python 3.12.x. On macOS and Linux the command may be python3; use that everywhere below.
2. Make a project folder and a virtual environment #
A virtual environment keeps this bot's packages separate from everything else on your computer.
mkdir my-bot
cd my-bot
python -m venv .venvActivate it. You need to do this each time you open a new terminal for the bot:
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activateYour prompt now starts with (.venv). Install the two packages:
python -m pip install -U discord.py python-dotenv
3. Put the token in a .env file #
Create a file called .env (with the dot) in the my-bot folder, with one line. Paste your token after the =, with no quotes or spaces:
DISCORD_TOKEN=paste-your-token-hereIf you'll use Git, create .gitignore too so the token never gets uploaded:
.env
.venv/
__pycache__/
data.db
4. Write bot.py #
Create bot.py in the same folder:
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv() # reads DISCORD_TOKEN from the .env file next to this script
TOKEN = os.getenv("DISCORD_TOKEN")
class MyBot(commands.Bot):
def __init__(self) -> None:
# Default intents are enough for slash commands. No privileged intents needed.
super().__init__(command_prefix=commands.when_mentioned, intents=discord.Intents.default())
async def setup_hook(self) -> None:
# Runs once, before the bot connects. Upload our slash commands to Discord.
synced = await self.tree.sync()
print(f"Synced {len(synced)} slash command(s)")
bot = MyBot()
@bot.event
async def on_ready() -> None:
print(f"Logged in as {bot.user} (ID: {bot.user.id})")
@bot.tree.command(name="ping", description="Check that the bot is alive")
async def ping(interaction: discord.Interaction) -> None:
await interaction.response.send_message(f"Pong! {round(bot.latency * 1000)} ms")
if not TOKEN:
raise SystemExit("DISCORD_TOKEN is missing. Put it in your .env file.")
bot.run(TOKEN)What the important lines do:
intents=discord.Intents.default()asks Discord for the normal events, without the privileged ones.setup_hookruns once before the bot connects.self.tree.sync()sends your slash commands to Discord. Only sync when your commands change; syncing in a loop gets you rate limited.@bot.tree.commandturns a function into a slash command. Itsdescriptionis what users see.async defandawait: discord.py does many things at once, so commands are async functions. Inside them, useawaitand nevertime.sleep().- Every slash command must answer within 3 seconds, here with
interaction.response.send_message.
5. Run it #
python bot.pyYou should see Synced 1 slash command(s) and then Logged in as YourBot#1234. The bot turns green in your server. Type /ping and pick your bot's command.
Don't see /ping? Press Ctrl+R (or restart the Discord app) to reload the command list. Still missing? See slash commands not showing.
Stop the bot with Ctrl+C. While it runs on your computer, it goes offline when you close the terminal; Part 5 fixes that.
If something goes wrong #
LoginFailure: Improper token has been passed: see fix improper token.ModuleNotFoundError: No module named 'discord': your virtual environment isn't active. See fix ModuleNotFoundError.PrivilegedIntentsRequired: you asked for an intent that's off in the portal. See fix privileged intents.
FAQ #
Why slash commands instead of !commands?
Slash commands show up in Discord's menu with descriptions and options, and they don't need the Message Content privileged intent. Discord recommends them for new bots.
Should I name my file discord.py?
No. A file called discord.py hides the real library and causes confusing errors. Use bot.py or main.py.
Do I have to sync every time the bot starts?
For a small bot it's fine, because syncing the same commands is quick. Don't sync in a loop or in on_ready, which can run many times.
Still stuck? Use the live chat on the panel or ask in the ESN Systems Discord. Last updated November 3, 2026.