Build a Discord Moderation Bot

Build a Python Discord bot with slash commands and auto-moderation.

Introduction

30 Second Summary

Every online community needs a way to keep conversations civil and organized. Moderating a busy server by hand gets exhausting fast.

In this project, you will build a Discord moderation bot using Python and the discord.py library. Your bot will respond to slash commands, welcome new members with custom messages, and kick troublemakers out of your server.

What You'll Build

A Discord moderation bot powered by Python and discord.py that handles slash commands, automates moderation, and welcomes new members with rich embedded messages.

By the end of this project, you'll have:

  • A registered bot application on the Discord Developer Portal with a secure token and configured Gateway Intents.
  • Hybrid slash commands (/hello, /poll, /kick) with parameter handling and permission checks.
  • An automatic welcome system that sends rich embed messages when new members join your server.
  • A Secret Mission challenge to add auto-moderation that catches and deletes messages containing banned words.

Are there any prerequisites?

This project assumes you have basic Python knowledge and VS Code installed on your computer. You'll also need a free Discord account.

Set Up Your Project

Every Discord bot is a Python script that connects to Discord's servers and responds to events like messages and commands. Before you can build your bot, you need the right library installed and a project ready to hold your code.

In this step, you'll install the discord.py library. You'll also create your project files and prepare a secure location for your bot's credentials.

In this step, get ready to:
  • Install the discord.py library.
  • Create your project folder and main bot file.
  • Set up a secure environment file for your bot token.
Install discord.py

discord.py is the Python library that lets your code talk to the Discord API. You need to install it before writing any bot logic.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click Terminal in the top menu bar.
  • Click New Terminal.
  • Install discord.py by running this command:
pip install -U discord.py

What does this command do?

  • pip is Python's package manager for downloading and installing libraries.
  • The -U flag upgrades the library to the latest version if an older one is already installed.
  • Confirm the library installed correctly by running this command:

macOS

python3 -c "import discord; print(discord.__version__)"

Windows

py -3 -c "import discord; print(discord.__version__)"

You should see 2.7.1 printed in the terminal.

Seeing 'command not found'?

  • If pip is not recognized, try running pip3 install -U discord.py instead.
  • If python3 or py is not recognized, make sure Python is installed and added to your system's PATH.
  • Help me troubleshoot my discord.py installation
Create your project folder and bot file
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir discord-mod-bot
cd discord-mod-bot
  • Confirm the folder was created by running:
ls

The output should be empty since the folder is brand new.

Now open this folder in VS Code so you can start adding files.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Select the discord-mod-bot folder on your Desktop.
  • Click Open (macOS) or Select Folder (Windows).
  • If VS Code asks whether you trust the authors, click Yes, I trust the authors.

The Explorer sidebar on the left should now show the empty discord-mod-bot folder.

  • In the Explorer sidebar, hover over the DISCORD-MOD-BOT header.
  • Click the New File icon that appears.
  • Type bot.py and press Enter.

You should see bot.py appear in the Explorer sidebar and open as an empty file.

Never name your file discord.py

Naming your file discord.py conflicts with the discord.py library. Python tries to import your file instead of the library, causing errors. Your bot file must have a different name, like bot.py.

Create an environment file for your token

Your bot needs a secret token to connect to Discord. Storing tokens directly in your code is risky because anyone who sees your code could take control of your bot.

  • Click the New File icon in the Explorer sidebar again.
  • Type .env and press Enter.
  • Add this line to your .env file:
DISCORD_TOKEN=your-token-here

What is a .env file?

A .env file stores configuration values like tokens and passwords outside of your code. This keeps your secrets safe if you ever share or publish your project.

  • Save the .env file by pressing Cmd+S (macOS) or Ctrl+S (Windows).

You'll replace your-token-here with your actual bot token in the next step.

Your Explorer sidebar should now show both bot.py and .env inside the discord-mod-bot folder.

Don't see both files?

  • Make sure you opened the discord-mod-bot folder itself, not a parent folder like your Desktop.
  • If the .env file is hidden, click the ... menu at the top of the Explorer sidebar and make sure Show Hidden Files is enabled.
  • Help me find my project files in VS Code

Your project is set up and ready to go. Next, you'll create your bot application on the Discord Developer Portal and invite it to a test server.

Create Your Bot on Discord

Your project folder is set up and your files are ready. Now you need to create an actual bot that Discord recognizes.

Every Discord bot starts as an "application" registered in the Discord Developer Portal. This is where you generate a secret token (your bot's password), configure what data it can access, and create an invite link to add it to your server.

In this step, get ready to:
  • Create a bot application in the Discord Developer Portal.
  • Configure Gateway Intents and save your bot token.
  • Invite the bot to a test server with the right permissions.
Create a new application

The Discord Developer Portal is the dashboard where you register and manage bot applications. Your bot needs to be registered here before it can connect to any server.

  • Open the Discord Developer Portal in your browser.
  • Sign in with your Discord account if prompted.
  • Click New Application in the top right corner.
  • Enter ModBot as the application name.
  • Accept the terms of service and click Create.
  • Click Bot in the left sidebar to open the Bot settings page.

You should see your bot's settings page with a username and icon section at the top.

What did I just create?

An "application" is Discord's container for your bot. It holds the bot account, permissions, and credentials. The Bot tab is where you control the bot user itself, including its token and what data it can access.

Configure intents and copy your token

Discord bots must declare what types of data they want to receive. These declarations are called Gateway Intents. Your moderation bot needs access to message content (to read messages) and member events (to detect when someone joins the server).

  • On the Bot page, scroll down to the Privileged Gateway Intents section.
  • Toggle on Message Content Intent.
  • Toggle on Server Members Intent.
  • Click Save Changes at the bottom of the page.

Why are these intents required?

These are "privileged" intents because they give your bot access to sensitive data. Message Content Intent lets your bot read what people type. Server Members Intent lets your bot detect when someone joins or leaves. Without these, your moderation and welcome features won't work.

Now you need to generate the bot's token. This is the secret password your Python code uses to log in as the bot.

  • Scroll back up to the top of the Bot page.
  • Click Reset Token.
  • Discord may ask you to confirm with your password or two-factor authentication. Complete the verification.
  • Click Copy to copy the token to your clipboard.

Copy your token now

Discord only shows the token once. If you navigate away without copying it, you will have to reset it again. Copy it immediately.

  • Open your .env file in VS Code.
  • Paste your token into the file so it looks like this:
DISCORD_TOKEN=your-token-here

What does this line do?

The DISCORD_TOKEN line stores your bot's secret token as an environment variable. Your Python code will read this value to log in. Keeping the token in a separate .env file means it never gets hardcoded into your source code.

  • Replace your-token-here with the actual token you just copied. Do not add quotes or spaces around it.
  • Save the .env file.

Pro tip: never share your token

Your bot token is like a password. Anyone who has it can control your bot. Never post it in a chat, commit it to Git, or share it in a screenshot. If you accidentally leak it, return to the Developer Portal and click Reset Token immediately.

Don't see the Reset Token button?

Make sure you are on the Bot tab in the left sidebar, not the General Information tab. The token controls are at the top of the Bot page, under the bot's username.

Help me find where to reset my bot token in the Discord Developer Portal.

Invite the bot to a test server

Your bot exists on Discord now, but it is not in any server yet. You need to generate an invite link with the right permissions and use it to add the bot to a server you control.

First, create a test server so you have a safe place to experiment.

  • Open Discord (the app or web version).
  • Click the + button at the bottom of your server list on the left side.
  • Select Create My Own.
  • Select For me and my friends.
  • Name the server something like Bot Testing and click Create.

Now generate the invite link in the Developer Portal.

  • Go back to the Discord Developer Portal and select your ModBot application.
  • Click OAuth2 in the left sidebar.
  • Click URL Generator under the OAuth2 section.

The URL Generator lets you pick what your bot is allowed to do. You need to select both scopes and permissions.

  • Under Scopes, check the bot checkbox.
  • Also check the applications.commands checkbox.

A new Bot Permissions section appears below. Select the permissions your bot needs.

  • Check Send Messages.
  • Check Manage Messages.
  • Check Kick Members.
  • Check Read Message History.

What do the scopes and permissions control?

  • The bot scope tells Discord this is a bot user that joins servers.
  • The applications.commands scope allows your bot to register slash commands. Without it, commands like /hello won't appear in Discord's autocomplete.
  • The four permissions grant the bot the ability to send messages, delete messages, kick users, and read past messages in channels.
  • Scroll to the bottom of the page and find the Generated URL section.
  • Click Copy to copy the invite URL.
  • Paste the URL into a new browser tab.
  • Select your Bot Testing server from the dropdown.
  • Click Authorize.
  • Complete the CAPTCHA if one appears.

Switch to your Discord app and open the Bot Testing server. You should see ModBot listed in the member list on the right side. Its status will show as offline for now.

Don't see the bot in your server?

  • Make sure you selected the correct server in the authorization dropdown. If you picked the wrong one, generate the invite URL again and try with the right server.
  • Check that both the bot and applications.commands scopes are checked. If either is missing, the invite URL won't work correctly.
  • Try clicking the member list icon at the top right of the channel to make sure the member sidebar is visible.

Help me troubleshoot why my Discord bot is not appearing in my server.

Your bot is registered, configured, and sitting in your test server. It is offline for now because no code is connecting it yet. In the next step, you will write the Python code that brings your bot to life and makes it respond.

Write Your First Bot and Connect It

Your bot is registered on Discord and sitting in your test server. Right now it's offline because there's no code powering it.

In this step, you'll write the Python code that brings your bot to life. You'll use discord.py to set up event listeners, connect to Discord's servers, and watch your bot go online for the first time.

In this step, get ready to:
  • Write the base bot setup with intents and an event handler.
  • Load your bot token and sync slash commands with Discord.
  • Run the bot and confirm it appears online in your server.
Write the base bot code

Discord bots work through event-driven programming. Your code listens for specific events (like the bot finishing its login) and runs a function in response.

  • Open bot.py in VS Code and add the following code:
import os
import discord
from discord.ext import commands

# Bot setup
intents = discord.Intents.default()
intents.message_content = True
intents.members = True

bot = commands.Bot(command_prefix="!", intents=intents)

What does this code do?

  • discord.Intents.default() creates a set of standard permissions for your bot. These control which events your bot can receive from Discord.
  • Setting message_content and members to True enables two privileged Gateway Intents. Your bot needs these to read message text and detect when members join.
  • commands.Bot creates your bot instance. The command_prefix="!" lets users run commands with ! in addition to slash commands.

You won't see any output yet. The next chunk adds the event handler that confirms your bot is connected.

  • Below the bot = commands.Bot(...) line, add this event handler:
@bot.event
async def on_ready():
    print(f"{bot.user} is online and ready!")

What does this code do?

  • @bot.event is a decorator that registers a function as an event listener. Discord.py calls it automatically when the matching event fires.
  • async def makes this an asynchronous function. Discord.py uses async/await throughout because your bot handles multiple events at the same time without blocking.
  • on_ready is a special event name that discord.py calls automatically once the bot finishes logging in and is ready to receive events.

What is async/await?

Python's async and await keywords let your program handle multiple tasks without waiting for each one to finish first. Your bot might be responding to a command while also listening for new members joining. Async programming makes this possible.

  • Save bot.py.

You won't see any output yet. The bot still needs a token to connect to Discord.

Load your token and sync commands

Your bot needs two more things before it can go online: a way to register slash commands with Discord, and a way to find its token so it can log in.

  • In bot.py, find the on_ready function you just wrote.
  • Find this code:
@bot.event
async def on_ready():
    print(f"{bot.user} is online and ready!")
  • Add await bot.tree.sync() as the first line inside the function, and add the comment above the decorator. Your function should now look like this:
# Sync slash commands on startup
@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"{bot.user} is online and ready!")

What does syncing do?

bot.tree.sync() registers your slash commands with Discord's servers. Without this call, any commands you define in your code won't appear in Discord's autocomplete menu when users type /.

  • At the bottom of bot.py, add this code to load your token and start the bot:
# Load token and run
token = os.getenv("DISCORD_TOKEN")
if token is None:
    # Fallback: read from .env file manually
    with open(".env") as f:
        for line in f:
            if line.startswith("DISCORD_TOKEN="):
                token = line.strip().split("=", 1)[1]

bot.run(token)

What does this code do?

  • os.getenv("DISCORD_TOKEN") checks if the token is set as a system environment variable.
  • If the environment variable isn't found, the fallback block reads the .env file directly and extracts the token. This keeps things simple without needing an extra library like python-dotenv.
  • bot.run(token) starts the bot's connection to Discord using your token. This line blocks the rest of the script, so it always goes at the very bottom of the file.
  • Save bot.py.

Seeing a file not found error?

  • Double-check that your .env file has the format DISCORD_TOKEN=your-token-here with no spaces around the = sign.
  • Make sure the .env file is in the same folder as bot.py.
  • Help me debug my .env token loading

Awesome, I've got everything!

Great. Double check you've saved bot.py before moving on.

I'd like to double check the full code

Here's the complete bot.py file so far. Make sure yours matches:

import os
import discord
from discord.ext import commands

# Bot setup
intents = discord.Intents.default()
intents.message_content = True
intents.members = True

bot = commands.Bot(command_prefix="!", intents=intents)


# Sync slash commands on startup
@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"{bot.user} is online and ready!")


# Load token and run
token = os.getenv("DISCORD_TOKEN")
if token is None:
    # Fallback: read from .env file manually
    with open(".env") as f:
        for line in f:
            if line.startswith("DISCORD_TOKEN="):
                token = line.strip().split("=", 1)[1]

bot.run(token)
Run and test the bot

Your code is ready. Time to see your bot come online.

  • In VS Code, open the terminal by pressing Cmd+` (macOS) or Ctrl+` (Windows).
  • Make sure you're in the discord-mod-bot folder, then run your bot:

macOS

python3 bot.py

Windows

py -3 bot.py

You should see a message like ModBot#1234 is online and ready! printed in your terminal (with your bot's actual name instead of ModBot).

  • Open Discord and navigate to your test server.
  • Check the member list on the right side of the screen. Your bot should appear as online with a green dot.

Bot not coming online?

  • If you see LoginFailure, your token is incorrect. Go back to the Discord Developer Portal, reset your token, and update your .env file with the new one.
  • If you see PrivilegedIntentsRequired, go to the Bot tab in the Developer Portal and make sure Message Content Intent and Server Members Intent are both enabled.
  • If you see FileNotFoundError, make sure your terminal is inside the discord-mod-bot folder where your .env file lives.
  • My Discord bot won't come online, help me debug this

Pro tip: keep your terminal open

Your bot stays online only while the script is running. If you close the terminal or press Ctrl+C, the bot goes offline. Keep the terminal open for the rest of this project.

Your bot is alive and online. Next up, you'll give it slash commands so it can actually respond when users interact with it.

Add Slash Commands

Your bot is online and connected to Discord. It needs commands that let users actually interact with it.

Slash commands are Discord's built-in way for users to talk to bots. In this step, you'll add three slash commands that bring your bot to life.

In this step, get ready to:
  • Add a /hello command that greets users by name.
  • Add a /poll command that creates emoji-based votes.
  • Add a /kick command with permission checks to moderate users.
Create a /hello command

discord.py supports something called hybrid commands. These are functions that work as both slash commands (the /hello style) and text commands (the !hello style).

What is a hybrid command?

A hybrid command registers your function with Discord's slash command system AND as a traditional text command using your bot's prefix (!). This means users can invoke it either way. If slash commands haven't synced yet, you can always test with the text prefix.

  • In bot.py, find the on_ready function you wrote in the last step. Add the following code below it, above the token loading section at the bottom of the file:
# /hello command
@bot.hybrid_command(name="hello", description="Get a friendly greeting from the bot")
async def hello(ctx):
    await ctx.send(f"Hello, {ctx.author.display_name}! 👋")

What does this code do?

  • @bot.hybrid_command() registers this function as both a slash command and a text command. The name and description show up in Discord's slash command menu.
  • ctx is the command context. It holds information about who sent the command and which channel it was sent in.
  • ctx.author.display_name grabs the server nickname of the person who ran the command.
  • ctx.send() sends a message back to the same channel where the command was used.
  • Save bot.py.
  • Press Ctrl+C in your terminal to stop the bot.
  • Restart the bot by running:

macOS

python3 bot.py

Windows

py -3 bot.py
  • In your Discord test server, type /hello and press Enter.

The bot should reply with "Hello, [your display name]!" along with a wave emoji.

Slash command not showing up?

  • Global slash commands can take up to an hour to appear in Discord's autocomplete after syncing. Try typing !hello instead. Since this is a hybrid command, it works with the text prefix right away.
  • Check your terminal for error messages. If you see Forbidden, make sure you included the applications.commands scope when generating your bot's invite URL in Step 2.
  • Make sure your code is placed between the on_ready function and the token loading section at the bottom. Code after bot.run() never executes.

Still stuck? Ask for help.

Create a /poll command

The next command lets users create quick yes-or-no polls. The bot posts the question and adds emoji reactions so members can vote directly on the message.

  • In bot.py, add the following code below the hello function you just wrote:
# /poll command
@bot.hybrid_command(name="poll", description="Create a yes/no poll")
async def poll(ctx, *, question: str):
    message = await ctx.send(f"📊 **Poll:** {question}")
    await message.add_reaction("👍")
    await message.add_reaction("👎")

What does this code do?

  • The * before question makes it a keyword-only parameter. In the slash command interface, Discord labels the parameter clearly so users know what to type.
  • question: str tells Discord this parameter expects a text string.
  • ctx.send() sends the poll question and stores the resulting message object in the message variable.
  • message.add_reaction() adds emoji reactions to that specific message so users can vote by clicking the emoji.
  • Save bot.py.
  • Press Ctrl+C to stop the bot, then restart it using the same run command from earlier.
  • In your test server, type /poll question: Should we have a movie night? and press Enter.

The bot should send a message with your poll question. You should see thumbs-up and thumbs-down reactions automatically added underneath it.

Not seeing emoji reactions?

  • Make sure you stored the result of ctx.send() in a variable called message. Without this, add_reaction() has no message to attach reactions to.
  • If the slash command is not showing up, try !poll Should we have a movie night? using the text prefix instead.

Need a hand? Ask for help.

Create a /kick command with permission checks

A moderation bot needs to restrict powerful actions to users who actually have permission. The /kick command should only work for server members who have the "Kick Members" permission in their Discord role.

  • In bot.py, add the following code below the poll function:
# /kick command
@bot.hybrid_command(name="kick", description="Kick a member from the server")
@commands.has_permissions(kick_members=True)
async def kick(ctx, member: discord.Member, *, reason: str = "No reason provided"):
    await member.kick(reason=reason)
    await ctx.send(f"👢 {member.display_name} has been kicked. Reason: {reason}")


@kick.error
async def kick_error(ctx, error):
    if isinstance(error, commands.MissingPermissions):
        await ctx.send("❌ You don't have permission to kick members.")

What does this code do?

  • @commands.has_permissions(kick_members=True) checks that the person running the command has the "Kick Members" permission in the server. If they don't, discord.py raises a MissingPermissions error instead of running the function.
  • member: discord.Member tells Discord this parameter expects a server member. Discord shows a member picker in the slash command interface so you can select who to kick.
  • reason: str = "No reason provided" makes the reason optional. If no reason is given, it defaults to "No reason provided".
  • @kick.error registers an error handler specifically for the kick command. Instead of a generic error, the bot sends a friendly "you don't have permission" message.

Why use a permission decorator?

The @commands.has_permissions() decorator checks the invoking user's permissions, not the bot's. The bot also needs the "Kick Members" permission in its own server role to actually perform the kick. You already granted this when you set up the bot's invite URL in Step 2.

  • Save bot.py.
  • Press Ctrl+C to stop the bot, then restart it using the same run command from earlier.
  • In your test server, type /kick to verify the command appears in Discord's autocomplete menu.

Pro tip: testing kick safely

You can't kick yourself from a server. To fully test this command, invite a friend to your test server or create a second Discord account. You can also verify the permission check works by temporarily removing your own "Kick Members" permission and trying the command.

Getting a permission error in the terminal?

  • If the bot itself gets a Forbidden error when trying to kick, the bot's server role does not have kick permissions. Go to your server's Settings, then Roles, find your bot's role, and enable Kick Members.
  • The bot cannot kick members whose role is higher than the bot's role in the server hierarchy. Make sure the bot's role is above the target member's role.

Still stuck? Ask for help.

Awesome, I've got everything!

Make sure you've saved bot.py with all three commands before moving on.

I'd like to double check the full code

Here is the full bot.py file as it should look at the end of this step.

import os
import discord
from discord.ext import commands

# Bot setup
intents = discord.Intents.default()
intents.message_content = True
intents.members = True

bot = commands.Bot(command_prefix="!", intents=intents)


# Sync slash commands on startup
@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"{bot.user} is online and ready!")


# /hello command
@bot.hybrid_command(name="hello", description="Get a friendly greeting from the bot")
async def hello(ctx):
    await ctx.send(f"Hello, {ctx.author.display_name}! 👋")


# /poll command
@bot.hybrid_command(name="poll", description="Create a yes/no poll")
async def poll(ctx, *, question: str):
    message = await ctx.send(f"📊 **Poll:** {question}")
    await message.add_reaction("👍")
    await message.add_reaction("👎")


# /kick command
@bot.hybrid_command(name="kick", description="Kick a member from the server")
@commands.has_permissions(kick_members=True)
async def kick(ctx, member: discord.Member, *, reason: str = "No reason provided"):
    await member.kick(reason=reason)
    await ctx.send(f"👢 {member.display_name} has been kicked. Reason: {reason}")


@kick.error
async def kick_error(ctx, error):
    if isinstance(error, commands.MissingPermissions):
        await ctx.send("❌ You don't have permission to kick members.")


# Load token and run
token = os.getenv("DISCORD_TOKEN")
if token is None:
    # Fallback: read from .env file manually
    with open(".env") as f:
        for line in f:
            if line.startswith("DISCORD_TOKEN="):
                token = line.strip().split("=", 1)[1]

bot.run(token)

Your bot can greet users, run polls, and kick troublemakers. Next up, you'll make it welcome new members automatically with a rich embed message the moment they join the server.

Add Welcome Messages with Embeds

Your bot can now respond to slash commands and moderate users. Those features all require someone to type a command first.

In this step, you'll make your bot react to server events automatically. When a new member joins, the bot will post a rich welcome message using a Discord Embed, complete with colors and a member count footer.

In this step, get ready to:
  • Add an event handler that detects new members joining.
  • Create a rich embed welcome message with colors and a footer.
  • Test the welcome feature in your server.
Listen for the on_member_join event

So far, every feature in your bot requires someone to type a command. Discord bots can also listen for server events like members joining, messages being deleted, or reactions being added.

The on_member_join event fires every time a new member joins the server. You already enabled the Server Members Intent in Steps 2 and 3, which is required for the bot to receive this event.

What is an event handler?

An event handler is a function that runs automatically when something specific happens. You already have one: on_ready runs when the bot connects to Discord. The on_member_join handler works the same way, but triggers when a new user joins your server.

Create a rich embed welcome message

Plain text messages work fine, but Discord Embeds let you send structured, colorful messages with titles, descriptions, and footers. They stand out in a channel and look professional.

  • If your bot is still running, press Ctrl+C in your terminal to stop it.
  • In bot.py, find the kick_error function at the bottom of your command handlers. Add the following code below it, before the # Load token and run section:
# Welcome new members
@bot.event
async def on_member_join(member):
    channel = member.guild.system_channel
    if channel is not None:
        embed = discord.Embed(
            title="Welcome! 🎉",
            description=f"Hey {member.mention}, welcome to **{member.guild.name}**! We're glad to have you here.",
            color=discord.Color.blue(),
        )
        embed.set_footer(text=f"Member #{member.guild.member_count}")
        await channel.send(embed=embed)

What does this code do?

  • @bot.event registers this function as an event handler. Discord calls it automatically when a member joins.
  • member.guild.system_channel finds the channel your server has configured for system messages (like the default welcome channel). It can be None if no channel is set, so the if channel is not None check prevents a crash.
  • discord.Embed() creates a rich message with a title, description, and blue color. The description uses member.mention to tag the new member directly.
  • set_footer() adds a small line at the bottom showing the total member count, so the new member knows how big the community is.
  • Save bot.py.
  • Run the bot to confirm there are no syntax errors by running:
py -3 bot.py
  • You should see the same is online and ready! message in your terminal. The welcome feature activates the next time someone joins your server.

Seeing a syntax error?

  • Make sure the on_member_join function is at the top level of your file (not indented inside another function).
  • Check that the f-string in the description has a matching pair of double quotes wrapping it and curly braces around member.mention and member.guild.name.
  • Verify that the new code block sits above your # Load token and run section. The bot.run(token) call must always be the last thing in the file.
  • Help me debug my on_member_join code

Awesome, I've got everything!

Double check that you've saved bot.py and the bot is running without errors.

I'd like to double check the full code

import os
import discord
from discord.ext import commands

# Bot setup
intents = discord.Intents.default()
intents.message_content = True
intents.members = True

bot = commands.Bot(command_prefix="!", intents=intents)


# Sync slash commands on startup
@bot.event
async def on_ready():
    await bot.tree.sync()
    print(f"{bot.user} is online and ready!")


# /hello command
@bot.hybrid_command(name="hello", description="Get a friendly greeting from the bot")
async def hello(ctx):
    await ctx.send(f"Hello, {ctx.author.display_name}! 👋")


# /poll command
@bot.hybrid_command(name="poll", description="Create a yes/no poll")
async def poll(ctx, *, question: str):
    message = await ctx.send(f"📊 **Poll:** {question}")
    await message.add_reaction("👍")
    await message.add_reaction("👎")


# /kick command
@bot.hybrid_command(name="kick", description="Kick a member from the server")
@commands.has_permissions(kick_members=True)
async def kick(ctx, member: discord.Member, *, reason: str = "No reason provided"):
    await member.kick(reason=reason)
    await ctx.send(f"👢 {member.display_name} has been kicked. Reason: {reason}")


@kick.error
async def kick_error(ctx, error):
    if isinstance(error, commands.MissingPermissions):
        await ctx.send("❌ You don't have permission to kick members.")


# Welcome new members
@bot.event
async def on_member_join(member):
    channel = member.guild.system_channel
    if channel is not None:
        embed = discord.Embed(
            title="Welcome! 🎉",
            description=f"Hey {member.mention}, welcome to **{member.guild.name}**! We're glad to have you here.",
            color=discord.Color.blue(),
        )
        embed.set_footer(text=f"Member #{member.guild.member_count}")
        await channel.send(embed=embed)


# Load token and run
token = os.getenv("DISCORD_TOKEN")
if token is None:
    # Fallback: read from .env file manually
    with open(".env") as f:
        for line in f:
            if line.startswith("DISCORD_TOKEN="):
                token = line.strip().split("=", 1)[1]

bot.run(token)
Test the welcome feature

The welcome message only triggers when a new member joins your server. You can't trigger it by typing a command, so you need a second account to test it.

  • Make sure your bot is running in the terminal.
  • Ask a friend to join your test server, or create a second Discord account and join with that.
  • Check the system messages channel in your server. You should see a blue embed with the title "Welcome!", a description tagging the new member, and a footer showing the member count.

Where is the system messages channel?

Every Discord server has a system messages channel configured in its settings. This is the channel where Discord posts its own join notifications. Your bot uses this same channel through member.guild.system_channel. By default, it is your server's first text channel.

Don't see the welcome message?

  • Make sure the Server Members Intent is enabled in the Discord Developer Portal under Bot > Privileged Gateway Intents. Without this, the bot never receives the join event.
  • Confirm that intents.members = True is set in your bot.py file near the top. The Developer Portal toggle alone is not enough.
  • Check that your server has a system messages channel configured. Open your server's Settings, then Overview, and look for the System Messages Channel setting. If it is disabled, the bot has no channel to send the embed to.
  • Help me troubleshoot my welcome message

Your bot now welcomes new members, responds to slash commands, and moderates users. Ready to take it further? The Secret Mission challenges you to add auto-moderation that scans messages for banned words.

Secret mission

Add Auto-Moderation

Your bot can kick users and welcome new members. But what about messages that break the rules? Build an auto-moderation feature that detects banned words, deletes the offending message, and warns the user automatically.

Before you go

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally with a free Discord application, so there are no ongoing costs.

Resources you used:

  • Discord bot application in the Discord Developer Portal.
  • Bot token stored in your .env file.
  • discord.py library installed via pip.
  • Project folder discord-mod-bot with bot.py.
  • Discord test server with the bot added.

Keep everything running

No action needed. Choose this if you're still actively building or want to keep experimenting with your bot.

  • Your bot will stay online as long as the Python script is running in your terminal.
  • Your Discord application and bot token remain active in the Developer Portal.
  • Your test server stays available for further testing.

Pause - I'll come back to this later

Shut down the bot process to free up your terminal. Your code and Discord application will still be there when you come back.

  • Press Ctrl + C in the terminal where your bot is running to stop it.
  • Your bot.py file and .env file will remain in your project folder.
  • Your Discord application and bot token stay active in the Developer Portal.
  • To restart later, open your terminal in the discord-mod-bot folder and run py -3 bot.py again.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Press Ctrl + C in the terminal to stop the bot if it is still running.
  • Delete the discord-mod-bot folder from your computer.
  • Go to the Discord Developer Portal at https://discord.com/developers/applications.
  • Select your bot application, scroll down, and click Delete App to remove it.
  • Optionally, delete the test Discord server by right-clicking the server icon, selecting Server Settings, then Delete Server.
  • Optionally, uninstall discord.py by running pip uninstall discord.py in your terminal.

Mission accomplished

That's a Wrap!

Nice work! You just built a fully functional Discord moderation bot from scratch using Python and discord.py.

You learned how to:

  • Registered a bot on the Discord Developer Portal and configured Gateway Intents to control what events your bot receives.
  • Built hybrid slash commands (/hello, /poll, /kick) with parameter handling and permission checks.
  • Created an event-driven welcome system that sends rich embed messages when new members join your server.
  • Added auto-moderation that detects banned words, deletes offending messages, and warns users.

Ready to quiz yourself?