ESN Docs

Docs › Discord bots

Code your first Discord bot in Python

Video: 18 minUpdated Nov 3, 2026

Beginner video tutorial and the full, tested discord.py 2 code: /ping, an /info embed, a welcome message and a safe /kick command, plus how to host the bot 24/7.

Watch the video #

Watch on YouTube

The video starts from zero: installing Python and VS Code, the Python basics you need (variables, functions, if, async/await), creating the bot in the Discord Developer Portal, then writing every line of the bot below.

Download the code #

Download esn-first-discord-bot-python.zip (bot.py, requirements.txt, .env.example, .gitignore and a README).

Full bot.py #

Works with discord.py 2.3 or newer (tested with 2.7.1) on Python 3.10+. It has /ping, /info (an embed), a welcome message in a channel named welcome, and /kick with permission checks and error handling.

import os

import discord
from discord import app_commands
from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")

intents = discord.Intents.default()
intents.members = True  # needed for welcome messages

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


@bot.event
async def setup_hook():
    synced = await bot.tree.sync()
    print(f"Synced {len(synced)} slash commands")


@bot.event
async def on_ready():
    print(f"Logged in as {bot.user}")


@bot.tree.command(name="ping", description="Check if the bot is online")
async def ping(interaction: discord.Interaction):
    ms = round(bot.latency * 1000)
    await interaction.response.send_message(f"Pong! {ms} ms")


@bot.tree.command(name="info", description="Show info about this server")
@app_commands.guild_only()
async def info(interaction: discord.Interaction):
    guild = interaction.guild
    embed = discord.Embed(
        title=guild.name,
        description="Here's some info about this server.",
        color=0x6C5CE7,
    )
    embed.add_field(name="Members", value=guild.member_count)
    created = guild.created_at.strftime("%b %d, %Y")
    embed.add_field(name="Created", value=created)
    if guild.icon:
        embed.set_thumbnail(url=guild.icon.url)
    embed.set_footer(text=f"Asked by {interaction.user.display_name}")
    await interaction.response.send_message(embed=embed)


@bot.event
async def on_member_join(member: discord.Member):
    channel = discord.utils.get(member.guild.text_channels, name="welcome")
    if channel is None:
        return
    server = member.guild.name
    embed = discord.Embed(
        title="Welcome!",
        description=f"Hey {member.mention}, welcome to **{server}**!",
        color=0x6C5CE7,
    )
    embed.set_thumbnail(url=member.display_avatar.url)
    await channel.send(embed=embed)


@bot.tree.command(name="kick", description="Kick a member from the server")
@app_commands.describe(member="Who to kick", reason="Reason for the kick")
@app_commands.default_permissions(kick_members=True)
@app_commands.checks.has_permissions(kick_members=True)
@app_commands.guild_only()
async def kick(
    interaction: discord.Interaction,
    member: discord.Member,
    reason: str = "No reason given",
):
    if member == interaction.user:
        await interaction.response.send_message(
            "You can't kick yourself.", ephemeral=True
        )
        return
    if member.top_role >= interaction.guild.me.top_role:
        await interaction.response.send_message(
            "That member's role is higher than mine.", ephemeral=True
        )
        return
    await member.kick(reason=reason)
    await interaction.response.send_message(f"Kicked {member} | {reason}")


@kick.error
async def kick_error(
    interaction: discord.Interaction, error: app_commands.AppCommandError
):
    if isinstance(error, app_commands.MissingPermissions):
        message = "You need the Kick Members permission."
    else:
        message = "Something went wrong. Check my permissions."
        print(f"/kick error: {error}")
    await interaction.response.send_message(message, ephemeral=True)


if not TOKEN:
    raise SystemExit("DISCORD_TOKEN is missing. Put it in your .env file.")

bot.run(TOKEN)

requirements.txt and .env #

requirements.txt:

discord.py>=2.3
python-dotenv

.env (in the same folder as bot.py; never share it or upload it to GitHub):

DISCORD_TOKEN=paste-your-token-here

.gitignore:

.env
__pycache__/

Discord Developer Portal settings #

  • Bot page: Reset Token, copy it into .env. If it ever leaks, reset it again.
  • Bot page: switch on Server Members Intent (the welcome message needs it).
  • OAuth2 → URL Generator: scopes bot and applications.commands; permissions View Channels, Send Messages, Embed Links and Kick Members. Open the URL and pick your server.
  • For /kick, the bot's role must be above the members it kicks (Server Settings → Roles).

More detail: How to create a Discord bot, get its token and invite it.

Run it #

python -m pip install -U discord.py python-dotenv
python bot.py

You should see Synced 3 slash commands and then Logged in as .... If you see Improper token has been passed, copy a fresh token into .env. If you see PrivilegedIntentsRequired, switch on Server Members Intent. See Common Discord bot errors.

Host it 24/7 on ESN #

  1. Pick a Discord bot plan at panel.esnoffical.com/plans (bot hosting from $0.50/month) and choose Discord bot (Python).
  2. In Files, upload bot.py, requirements.txt and .env to the top folder. See Uploading files.
  3. In Startup, set Main Python File to bot.py. See Startup settings.
  4. In Console, press Start. Each start runs pip install -r requirements.txt, then your bot. Wait for Logged in as.
  5. Stop the copy on your own computer, so only one copy of the bot runs.

Full guide: How to host a Python Discord bot on ESN Hosting. Want to go further? Try the 5-part Python course.

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