Welcome new members (discord.py)

The Python version of the welcome bot — greet joiners with an embed, and enable the Server Members intent both in code and in the Developer Portal.

This is the discord.js welcome bot rewritten for discord.py: an embed posted in your welcome channel whenever someone joins. Python needs the same privileged intent, enabled in two places — the toggle in the Developer Portal and a line in your code.

At a glance
You need a working bot from Host a discord.py bot, and a channel to post welcomes in
Plan Free or premium
Time About fifteen minutes

Build the discord.py starter first if you haven't — you need a bot with your token in .env on a server running the Python application.

Turn on the Server Members intent first

Member-join events are gated behind a privileged intent. discord.py won't even connect if your code asks for one that isn't enabled in the portal.

⚠️ Heads up — this is a privileged intent. In the Discord Developer Portal, open your application → BotPrivileged Gateway Intents → turn on Server Members Intent → save. The code sets intents.members = True; if the portal toggle is off, the bot raises PrivilegedIntentsRequired at startup instead of running. Bots in more than 100 servers must be verified before Discord grants this intent.

The code

The complete app.py. It reads the welcome channel's ID from .env:

import os

import discord
from dotenv import load_dotenv

load_dotenv()

intents = discord.Intents.default()
intents.members = True  # Server Members intent — privileged

client = discord.Client(intents=intents)

WELCOME_CHANNEL_ID = int(os.environ["WELCOME_CHANNEL_ID"])


@client.event
async def on_ready():
    print(f"Listening as {client.user}", flush=True)


@client.event
async def on_member_join(member: discord.Member):
    channel = member.guild.get_channel(WELCOME_CHANNEL_ID)
    if channel is None:
        return

    embed = discord.Embed(
        title="👋 Welcome!",
        description=f"Hey {member.mention}, welcome to **{member.guild.name}**!",
        color=0x57F287,
    )
    embed.set_thumbnail(url=member.display_avatar.url)
    embed.add_field(name="Member", value=str(member), inline=True)
    embed.add_field(name="You are member #", value=str(member.guild.member_count), inline=True)
    embed.timestamp = discord.utils.utcnow()

    await channel.send(content=member.mention, embed=embed)


client.run(os.environ["DISCORD_TOKEN"])

Add the channel ID to .env alongside the token:

DISCORD_TOKEN=your-token
WELCOME_CHANNEL_ID=123456789012345678

Get the ID by enabling Developer Mode in Discord (User Settings → Advanced), then right-clicking the channel and choosing Copy Channel ID. Save and restart.

The parts that matter

  • intents.members = True is the code half of the privileged intent. It has to be paired with the portal toggle, or discord.py refuses to start.
  • int(os.environ["WELCOME_CHANNEL_ID"]) — channel IDs come out of .env as strings, and get_channel wants an integer, so the int(...) conversion is required.
  • on_member_join is discord.py's join event. Its member argument gives you member.guild, member.mention, and member.display_avatar.
  • member.mention placed in content (not the embed) is what actually pings the new member — mentions buried inside an embed don't notify anyone.
  • discord.utils.utcnow() is the timezone-aware "now" discord.py expects for embed.timestamp; a plain datetime.now() warns about missing timezone info.

Making it yours

  • Use the system channel. Replace the lookup with member.guild.system_channel to post wherever Discord already routes system messages — no ID to configure.
  • DM the newcomer. await member.send("Welcome!") messages them privately. Wrap it in try/except discord.Forbidden — people who block server DMs will raise it.
  • Add a leave message. An on_member_remove event uses the same member object and the same intent.
  • Assign a role too. With the intent already on, Give every new member a role is a small addition to this handler.

Troubleshooting

  • PrivilegedIntentsRequired at startup — the Server Members Intent is off in the portal while your code requests it. Enable it (see the callout) and restart. More in Bot appears offline.
  • Bot runs but never welcomes anyone — the intent isn't really enabled, or WELCOME_CHANNEL_ID points at the wrong channel. Re-copy the ID and confirm the bot can post there.
  • KeyError: 'WELCOME_CHANNEL_ID' — the variable isn't in .env, or load_dotenv() runs after you read it. Keep load_dotenv() at the top.
  • The message posts but doesn't ping — the mention must be in content, which the code does with content=member.mention.

Next steps

Was this guide helpful?