ESN Docs

Docs › Discord bots › Course

Part 2: Hello world with a /ping slash command

3 min readUpdated Nov 3, 2026

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.

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 --version

It 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 .venv

Activate 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/activate

Your 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-here

If 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_hook runs 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.command turns a function into a slash command. Its description is what users see.
  • async def and await: discord.py does many things at once, so commands are async functions. Inside them, use await and never time.sleep().
  • Every slash command must answer within 3 seconds, here with interaction.response.send_message.

5. Run it #

python bot.py

You 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 #

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.

Want your bot online 24/7? Part 5 shows how to run it on ESN Hosting, with bot hosting from $0.50/month. Read Part 5 or see bot plans.

Still stuck? Use the live chat on the panel or ask in the ESN Systems Discord. Last updated November 3, 2026.