Building a Discord bot is a fun, practical Python project that teaches async programming, APIs, and event handling. In 2026, discord.py makes it straightforward. This guide builds a functional bot with slash commands, embeds, and interactive buttons, then deploys it.
📋 Table of Contents
Step 1: Create a Discord Application
- Go to the Discord Developer Portal (discord.com/developers)
- Click New Application, name your bot
- Go to the Bot section, click Add Bot
- Copy the Token (keep it secret — never commit it)
- Enable Message Content Intent under Privileged Gateway Intents
- Under OAuth2, then URL Generator: select
botandapplications.commandsscopes, choose permissions, and use the generated URL to invite the bot to your server
Step 2: Setup
pip install discord.py python-dotenv
# Project structure
mybot/
├── bot.py
├── .env
└── requirements.txt
# .env — never commit this
DISCORD_TOKEN=your_bot_token_here
Step 3: Basic Bot with Slash Commands
# bot.py
import os
import discord
from discord import app_commands
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
# Sync slash commands with Discord
await bot.tree.sync()
print(f"Logged in as {bot.user}")
# Slash command
@bot.tree.command(name="hello", description="Greet the user")
async def hello(interaction: discord.Interaction):
await interaction.response.send_message(
f"Hello, {interaction.user.mention}!"
)
# Slash command with parameters
@bot.tree.command(name="add", description="Add two numbers")
async def add(interaction: discord.Interaction, a: int, b: int):
await interaction.response.send_message(f"{a} + {b} = {a + b}")
bot.run(os.getenv("DISCORD_TOKEN"))
Step 4: Rich Embeds
@bot.tree.command(name="userinfo", description="Show user information")
async def userinfo(interaction: discord.Interaction, member: discord.Member = None):
member = member or interaction.user
embed = discord.Embed(
title=f"User Info: {member.display_name}",
color=discord.Color.blurple()
)
embed.set_thumbnail(url=member.display_avatar.url)
embed.add_field(name="Username", value=str(member), inline=True)
embed.add_field(name="ID", value=member.id, inline=True)
embed.add_field(
name="Joined",
value=member.joined_at.strftime("%Y-%m-%d"),
inline=False
)
embed.set_footer(text="MyBot")
await interaction.response.send_message(embed=embed)
Step 5: Interactive Buttons
class ConfirmView(discord.ui.View):
def __init__(self):
super().__init__(timeout=60)
self.value = None
@discord.ui.button(label="Confirm", style=discord.ButtonStyle.green)
async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button):
self.value = True
await interaction.response.send_message("Confirmed!", ephemeral=True)
self.stop()
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.red)
async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button):
self.value = False
await interaction.response.send_message("Cancelled.", ephemeral=True)
self.stop()
@bot.tree.command(name="confirm", description="Ask for confirmation")
async def confirm_cmd(interaction: discord.Interaction):
view = ConfirmView()
await interaction.response.send_message("Are you sure?", view=view)
Step 6: Event Handling
# Welcome new members
@bot.event
async def on_member_join(member):
channel = member.guild.system_channel
if channel:
await channel.send(f"Welcome {member.mention} to the server!")
# React to messages
@bot.event
async def on_message(message):
if message.author == bot.user:
return # ignore the bot's own messages
if "hello bot" in message.content.lower():
await message.channel.send("Hello there!")
await bot.process_commands(message) # keep prefix commands working
Step 7: Deploy the Bot
# Bots need to run 24/7. Deploy to a small VPS or Railway/Fly.io.
# On a VPS with systemd — /etc/systemd/system/mybot.service
[Unit]
Description=Discord Bot
After=network.target
[Service]
WorkingDirectory=/opt/mybot
ExecStart=/opt/mybot/venv/bin/python bot.py
Restart=always
EnvironmentFile=/opt/mybot/.env
[Install]
WantedBy=multi-user.target
# Enable and start
sudo systemctl enable mybot
sudo systemctl start mybot
Frequently Asked Questions
Q: Slash commands vs prefix commands?
A: Slash commands (/command) are the modern standard — Discord shows them in a menu with parameter hints. Prefix commands (!command) still work but are legacy. Use slash commands for new bots.
Q: Why aren’t my slash commands showing up?
A: You must call bot.tree.sync() to register them with Discord (done in on_ready above). Global sync can take up to an hour to propagate; sync to a specific guild for instant testing.
Q: How do I keep my bot token secure?
A: Store it in a .env file (gitignored), never hardcode it, and never commit it. If leaked, regenerate it immediately in the Developer Portal — a leaked token lets anyone control your bot.
Q: Where should I host my bot for free/cheap?
A: Railway and Fly.io have free/low-cost tiers. A $4-6/month VPS (Hetzner, Contabo) with systemd is reliable and cheap. Avoid free tiers that sleep on inactivity — bots need to run continuously.
Q: How do I add a database to my bot?
A: SQLite for simple bots (via aiosqlite for async), or PostgreSQL for larger ones (asyncpg). Store per-server settings, user data, or command state. Always use async database libraries to avoid blocking the bot.
Conclusion
Building a Discord bot with Python is an approachable project that teaches async programming and API integration. The discord.py library handles the hard parts — you define slash commands, rich embeds, and interactive buttons with clean decorators. Once your bot works locally, deploy it to a small VPS with systemd (or Railway/Fly.io) so it runs 24/7. From here, add a database, more commands, and event handlers to grow your bot into whatever your community needs.
📚 You might also like
🔗 Share this article




✍️ Leave a Comment