Discord bots power everything from community moderation to game integrations to AI chatbots. With over 200 million monthly active users, Discord is one of the best platforms for deploying automation tools.
This guide covers building a production-ready Discord bot with Python — from basic setup to slash commands, embeds, role management, scheduled tasks, and deployment.
First, set up your bot on the Discord Developer Portal:
.env file.
# Create project directory
mkdir discord-bot && cd discord-bot
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install dependencies
pip install discord.py python-dotenv aiohttp
# Create .env file
echo "DISCORD_TOKEN=your_token_here" > .env
echo ".env" >> .gitignore
import discord
from discord.ext import commands
from dotenv import load_dotenv
import os
load_dotenv()
# Bot configuration
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
bot = commands.Bot(
command_prefix="!",
intents=intents,
description="My awesome bot"
)
@bot.event
async def on_ready():
print(f"✅ {bot.user} is online!")
print(f"📊 Connected to {len(bot.guilds)} servers")
# Sync slash commands
await bot.tree.sync()
@bot.event
async def on_member_join(member):
channel = member.guild.system_channel
if channel:
embed = discord.Embed(
title=f"Welcome {member.display_name}! 👋",
description=f"You are member #{member.guild.member_count}",
color=discord.Color.green()
)
embed.set_thumbnail(url=member.avatar.url if member.avatar else "")
await channel.send(embed=embed)
bot.run(os.getenv("DISCORD_TOKEN"))
Discord deprecated prefix commands for verified bots. Use slash commands instead:
from discord import app_commands
@bot.tree.command(name="ping", description="Check bot latency")
async def ping(interaction: discord.Interaction):
latency = round(bot.latency * 1000)
await interaction.response.send_message(
f"🏓 Pong! Latency: {latency}ms"
)
@bot.tree.command(name="userinfo", description="Get user information")
@app_commands.describe(member="The member to inspect")
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=member.color
)
embed.add_field(name="ID", value=member.id, inline=True)
embed.add_field(name="Joined", value=member.joined_at.strftime("%Y-%m-%d"), inline=True)
embed.add_field(name="Roles", value=", ".join(r.name for r in member.roles[1:]) or "None")
embed.set_thumbnail(url=member.avatar.url if member.avatar else "")
await interaction.response.send_message(embed=embed)
@bot.tree.command(name="poll", description="Create a quick poll")
@app_commands.describe(question="The poll question", options="Comma-separated options")
async def poll(interaction: discord.Interaction, question: str, options: str):
choices = [o.strip() for o in options.split(",")][:10]
emojis = ["1️⃣","2️⃣","3️⃣","4️⃣","5️⃣","6️⃣","7️⃣","8️⃣","9️⃣","🔟"]
description = "\n".join(f"{emojis[i]} {choice}" for i, choice in enumerate(choices))
embed = discord.Embed(
title=f"📊 {question}",
description=description,
color=discord.Color.blue()
)
embed.set_footer(text=f"Poll by {interaction.user.display_name}")
await interaction.response.send_message(embed=embed)
msg = await interaction.original_response()
for i in range(len(choices)):
await msg.add_reaction(emojis[i])
@bot.tree.command(name="role", description="Assign or remove a role")
@app_commands.describe(member="Target member", role="Role to assign/remove")
@app_commands.checks.has_permissions(manage_roles=True)
async def manage_role(
interaction: discord.Interaction,
member: discord.Member,
role: discord.Role
):
if role >= interaction.guild.me.top_role:
await interaction.response.send_message(
"❌ I can't manage roles higher than mine!", ephemeral=True
)
return
if role in member.roles:
await member.remove_roles(role)
await interaction.response.send_message(
f"✅ Removed **{role.name}** from {member.mention}"
)
else:
await member.add_roles(role)
await interaction.response.send_message(
f"✅ Added **{role.name}** to {member.mention}"
)
# Self-assignable roles via reaction
ROLE_MESSAGE_ID = 123456789 # Your message ID
ROLE_EMOJI_MAP = {
"🐍": "Python",
"🌐": "Web Dev",
"🤖": "AI/ML",
}
@bot.event
async def on_raw_reaction_add(payload):
if payload.message_id != ROLE_MESSAGE_ID:
return
guild = bot.get_guild(payload.guild_id)
role_name = ROLE_EMOJI_MAP.get(str(payload.emoji))
if role_name:
role = discord.utils.get(guild.roles, name=role_name)
if role:
member = guild.get_member(payload.user_id)
await member.add_roles(role)
from discord.ext import tasks
import aiohttp
@tasks.loop(hours=1)
async def status_update():
"""Rotate bot status every hour"""
statuses = [
discord.Game("with Python 🐍"),
discord.Activity(type=discord.ActivityType.watching, name="the server"),
discord.Activity(type=discord.ActivityType.listening, name="/help"),
]
import random
await bot.change_presence(activity=random.choice(statuses))
@tasks.loop(minutes=30)
async def check_api():
"""Monitor an API and post alerts"""
channel = bot.get_channel(ALERT_CHANNEL_ID)
async with aiohttp.ClientSession() as session:
try:
async with session.get("https://api.example.com/status") as resp:
data = await resp.json()
if data.get("status") != "ok":
embed = discord.Embed(
title="⚠️ API Alert",
description=f"Status: {data.get('status')}",
color=discord.Color.red()
)
await channel.send(embed=embed)
except Exception as e:
await channel.send(f"🔴 API check failed: {e}")
@bot.event
async def on_ready():
status_update.start()
check_api.start()
await bot.tree.sync()
print(f"✅ {bot.user} is online!")
@bot.tree.command(name="clear", description="Delete messages in bulk")
@app_commands.describe(amount="Number of messages to delete (1-100)")
@app_commands.checks.has_permissions(manage_messages=True)
async def clear(interaction: discord.Interaction, amount: int):
if not 1 <= amount <= 100:
await interaction.response.send_message("Amount must be 1-100", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
deleted = await interaction.channel.purge(limit=amount)
await interaction.followup.send(f"🗑️ Deleted {len(deleted)} messages", ephemeral=True)
@bot.tree.command(name="timeout", description="Timeout a member")
@app_commands.describe(member="Member to timeout", duration="Duration in minutes", reason="Reason")
@app_commands.checks.has_permissions(moderate_members=True)
async def timeout_member(
interaction: discord.Interaction,
member: discord.Member,
duration: int,
reason: str = "No reason provided"
):
from datetime import timedelta
await member.timeout(timedelta(minutes=duration), reason=reason)
embed = discord.Embed(
title="⏰ Member Timed Out",
description=f"{member.mention} has been timed out for {duration} minutes",
color=discord.Color.orange()
)
embed.add_field(name="Reason", value=reason)
embed.add_field(name="Moderator", value=interaction.user.mention)
await interaction.response.send_message(embed=embed)
@bot.tree.error
async def on_app_command_error(
interaction: discord.Interaction,
error: app_commands.AppCommandError
):
if isinstance(error, app_commands.MissingPermissions):
await interaction.response.send_message(
"❌ You don't have permission to use this command.",
ephemeral=True
)
elif isinstance(error, app_commands.CommandOnCooldown):
await interaction.response.send_message(
f"⏳ Slow down! Try again in {error.retry_after:.1f}s",
ephemeral=True
)
else:
await interaction.response.send_message(
"❌ Something went wrong. Please try again.",
ephemeral=True
)
print(f"Command error: {error}")
# Global rate limit
@bot.tree.command(name="ai", description="Ask the AI a question")
@app_commands.checks.cooldown(1, 10.0) # 1 use per 10 seconds
async def ai_command(interaction: discord.Interaction, question: str):
await interaction.response.defer()
# Your AI logic here
await interaction.followup.send(f"🤖 Response to: {question}")
For larger bots, organize commands into cogs:
# cogs/moderation.py
import discord
from discord.ext import commands
from discord import app_commands
class Moderation(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="ban", description="Ban a member")
@app_commands.checks.has_permissions(ban_members=True)
async def ban(self, interaction: discord.Interaction,
member: discord.Member, reason: str = None):
await member.ban(reason=reason)
await interaction.response.send_message(
f"🔨 {member} has been banned. Reason: {reason or 'None'}"
)
async def setup(bot):
await bot.add_cog(Moderation(bot))
# main.py — load cogs
import pathlib
@bot.event
async def on_ready():
for cog_file in pathlib.Path("cogs").glob("*.py"):
if cog_file.stem != "__init__":
await bot.load_extension(f"cogs.{cog_file.stem}")
await bot.tree.sync()
print(f"✅ {bot.user} online with {len(bot.cogs)} cogs")
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
# /etc/systemd/system/discord-bot.service
[Unit]
Description=Discord Bot
After=network.target
[Service]
User=botuser
WorkingDirectory=/home/botuser/discord-bot
ExecStart=/home/botuser/discord-bot/venv/bin/python main.py
Restart=always
RestartSec=10
EnvironmentFile=/home/botuser/discord-bot/.env
[Install]
WantedBy=multi-user.target
discord.py 2.4+ for the latest features including app commands, modals, buttons, and select menus. The library is actively maintained and fully supports Python 3.12+.
Get automation scripts, bot templates, scraper blueprints, and AI tools — all copy-paste ready.
Get the AI Developer Toolkit — $19