NapNodes

← Discord bot guides

Discord bots · updated 24 October 2026

Discord Welcome Bot: Join Message and Auto Role (Python and JavaScript)

A message that greets each person by name, with their avatar, and a role handed out automatically when they join: it's the first thing people want from their own bot. It's 60 lines in Python (discord.py) or JavaScript (discord.js), and almost everything that goes wrong isn't the code but three Discord settings. Here are the code and the three settings.

Before coding: what Discord already does on its own

If a greeting is all you need, Discord has it built in: in “Server Settings”, on the first screen, pick the system messages channel and turn on the random welcome message. And Community servers have “Onboarding”, which gives roles based on what each person answers when they join.

Your own bot does what those can't: your own text, a card with the avatar and member number, the mention so they get a notification, a role for everyone who joins (or only once they accept the rules) and whatever you want to add later, like a goodbye message.

Rather not set it up yourself? Discord bot hosting from €1.50 a month, no contract and a full refund within 72 hours. Or create a free Discord bot, no card needed.

1. Create the bot and turn on “Server Members Intent”

  1. Create the application and get the token

    Go to discord.com/developers/applications, “New Application”; in the “Bot” tab, “Reset Token” and copy the token. It's the bot's password: don't share it or push it to GitHub. If this is your first bot, the guide for a bot in Python or in JavaScript goes through it slowly.

  2. Turn on “Server Members Intent”

    In the same “Bot” tab, under “Privileged Gateway Intents”, switch on “Server Members Intent” and save. It's the most forgotten step: without it, Discord doesn't tell the bot who joins and the bot does nothing, with no error at all. And if the code asks for it while it's off here, the bot won't even start (discord.py says PrivilegedIntentsRequired; discord.js, “Used disallowed intents”).

  3. Invite it with just the permissions it needs

    In “OAuth2” → “URL Generator”, tick “bot” and “applications.commands”, and for permissions “View Channels”, “Send Messages”, “Embed Links” and “Manage Roles”. Open the link it gives you and add it to your server.

2. Move the bot's role above the role it gives

A bot can only give roles that sit below its own role in the list. When it joins, Discord creates a role with its name and puts it at the very bottom, so the first attempt fails with “Missing Permissions” (code 50013) even though you gave it “Manage Roles”.

In “Server Settings” → “Roles”, drag the bot's role above the role it has to give (for example, “Member”) and save. And make sure that role has no dangerous permissions: anyone who joins will get it.

3. Get the channel and role ids

The bot finds the channel and the role by their id, not their name: that way it keeps working even if you rename them. To see them, in “User Settings” → “Advanced”, turn on “Developer Mode”. Then right-click the welcome channel → “Copy Channel ID”, and in “Server Settings” → “Roles”, right-click the role → “Copy Role ID”. Both go in the .env file. If you don't want to give any role, leave NEW_MEMBER_ROLE empty.

4. The code in Python (discord.py)

Three files in one folder. With Python 3.10 or newer: “pip install -r requirements.txt” and “python app.py”. When it says “Logged in as…”, it's already greeting people.

requirements.txt
discord.py
python-dotenv
.env
DISCORD_TOKEN=paste-your-token-here
WELCOME_CHANNEL=channel-id
NEW_MEMBER_ROLE=role-id
app.py
import os

import discord
from discord import app_commands
from dotenv import load_dotenv

load_dotenv()  # reads the token and the ids from the .env file

CHANNEL_ID = int(os.environ["WELCOME_CHANNEL"])
ROLE_ID = int(os.environ.get("NEW_MEMBER_ROLE") or 0)  # 0 = don't give any role

intents = discord.Intents.default()
intents.members = True  # without this, Discord doesn't tell the bot who joins
client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)


def card(member: discord.Member) -> discord.Embed:
    embed = discord.Embed(
        title=f"Welcome to {member.guild.name}!",
        description=f"Hi, {member.mention}. Have a look at the rules and introduce yourself.",
        color=0x5B4AE8,
    )
    embed.set_thumbnail(url=member.display_avatar.url)
    embed.set_footer(text=f"You're member number {member.guild.member_count}")
    return embed


async def give_role(member: discord.Member):
    role = member.guild.get_role(ROLE_ID)
    if role and role not in member.roles:
        await member.add_roles(role, reason="Welcome role")


@client.event
async def on_member_join(member: discord.Member):
    channel = member.guild.get_channel(CHANNEL_ID)
    if channel:
        # the mention goes outside the embed: inside it, they don't get pinged
        await channel.send(content=member.mention, embed=card(member))
    if not member.pending:  # if the server asks to accept the rules, wait
        await give_role(member)


@client.event
async def on_member_update(before: discord.Member, after: discord.Member):
    if before.pending and not after.pending:  # just accepted the rules
        await give_role(after)


@tree.command(name="test-welcome", description="Shows you the welcome message")
@app_commands.default_permissions(manage_guild=True)
@app_commands.guild_only()
async def test_welcome(interaction: discord.Interaction):
    await interaction.response.send_message(embed=card(interaction.user), ephemeral=True)


@client.event
async def on_ready():
    await tree.sync()  # publishes /test-welcome
    print(f"Logged in as {client.user}")


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

5. Or in JavaScript (discord.js)

The same with Node.js 20 or newer: “npm install” and “node index.js”. The .env is the same as the Python one.

package.json
{
  "name": "welcome-bot",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "discord.js": "^14.27.0",
    "dotenv": "^16.4.0"
  }
}
index.js
require("dotenv").config(); // reads the token and the ids from the .env file
const {
  Client, EmbedBuilder, Events, GatewayIntentBits, InteractionContextType,
  MessageFlags, PermissionFlagsBits, SlashCommandBuilder,
} = require("discord.js");

// GuildMembers: without this, Discord doesn't tell the bot who joins
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] });

function card(member) {
  return new EmbedBuilder()
    .setColor(0x5b4ae8)
    .setTitle(`Welcome to ${member.guild.name}!`)
    .setDescription(`Hi, ${member}. Have a look at the rules and introduce yourself.`)
    .setThumbnail(member.displayAvatarURL())
    .setFooter({ text: `You're member number ${member.guild.memberCount}` });
}

async function giveRole(member) {
  const role = process.env.NEW_MEMBER_ROLE;
  if (role && !member.roles.cache.has(role)) await member.roles.add(role, "Welcome role");
}

client.on(Events.GuildMemberAdd, async (member) => {
  const channel = member.guild.channels.cache.get(process.env.WELCOME_CHANNEL);
  // the mention goes outside the embed: inside it, they don't get pinged
  if (channel) await channel.send({ content: `${member}`, embeds: [card(member)] });
  if (!member.pending) await giveRole(member); // if the server asks to accept the rules, wait
});

client.on(Events.GuildMemberUpdate, async (before, after) => {
  if (before.pending && !after.pending) await giveRole(after); // just accepted the rules
});

const testWelcome = new SlashCommandBuilder()
  .setName("test-welcome")
  .setDescription("Shows you the welcome message")
  .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
  .setContexts(InteractionContextType.Guild);

client.on(Events.InteractionCreate, async (i) => {
  if (!i.isChatInputCommand() || i.commandName !== "test-welcome") return;
  await i.reply({ embeds: [card(i.member)], flags: MessageFlags.Ephemeral });
});

client.once(Events.ClientReady, async (c) => {
  await c.application.commands.set([testWelcome.toJSON()]); // publishes /test-welcome
  console.log(`Logged in as ${c.user.tag}`);
});

client.login(process.env.DISCORD_TOKEN);

If your server asks members to accept the rules

On servers with rules screening, whoever joins stays “pending” until they accept them. A bot that gives the role right on joining skips that check: with the role, they can already chat without having accepted anything. That's why the code checks whether the member is pending and, if so, waits until they accept before giving the role. On a server without screening, nobody is pending and the role arrives straight away.

The greeting is still sent on joining. If you'd rather greet only those who accept, move the message to the part that gives the role.

Test it without waiting for someone to join

The bot comes with a command, /test-welcome, that shows you the card exactly as a newcomer will see it, only to you. Only people who can manage the server see it. The first time, Discord can take a few minutes to show the command.

To test the role you need someone to actually join: a friend or a second account of yours. If the message shows up and the role doesn't, it's the hierarchy from step 2; if nothing shows up, it's the “Server Members Intent” from step 1 or the channel id.

Ideas to take it further

Goodbye message: the on_member_remove event in discord.py (GuildMemberRemove in discord.js) fires when someone leaves, with the same intent; you just send another message.

Direct message on joining: member.send(), the same in both libraries. Lots of people have DMs closed and it fails there, so wrap it so the error doesn't stop the rest (try/except discord.Forbidden, or a .catch()).

Image card, with the avatar on a background: it's drawn with Pillow in Python or @napi-rs/canvas in Node.js and sent as a file. It uses a bit more memory and CPU than an embed.

Several servers: this bot uses a fixed channel and role. For it to work on more than one server, store each one's channel and role (a JSON file is enough to begin with) with a command to set it up.

6. Keep it online all the time (for free)

The bot only greets people while its program is running: if it runs on your computer and you turn it off, whoever joins that night gets neither a message nor a role.

On NapNodes: create the free bot; if it's the Python one, switch it in “Settings” → “Language” (it starts out on Node.js), upload the files in “Files” (or set your GitHub repository, without the .env, and upload the .env separately) and click “Start”. The libraries install themselves at startup; app.py or index.js is the startup file that comes set.

The free bot has 256 MB, more than enough for a welcome bot, and you only have to renew it with one click every week. If you'd rather not have to remember, the Mini plan (512 MB) needs no renewing at all.

Frequently asked questions

Why doesn't my welcome bot do anything when someone joins?

Almost always “Server Members Intent” is missing: it has to be switched on in the Developer Portal (“Bot” tab) and requested in the code (intents.members in discord.py, GatewayIntentBits.GuildMembers in discord.js). Without it, Discord doesn't tell the bot who joins.

Why do I get “Missing Permissions” when giving the role?

Because the bot's role is below the role it's trying to give, or it's missing “Manage Roles”. In “Server Settings” → “Roles”, move the bot's role above it.

Do I need a bot to welcome people on Discord?

For a random greeting, no: Discord has it in the server settings. For your own message, the avatar, the mention or giving a role to everyone who joins, yes.

How do I give the role only once they accept the rules?

By waiting until the member is no longer pending: the code in this guide does that, with on_member_update in Python and GuildMemberUpdate in JavaScript.

Where can I keep my welcome bot online all the time?

On a server that doesn't switch off. NapNodes has a free 256 MB bot that you only have to renew with one click every week.

Shall we set it up for you?

Discord bot hosting from €1.50 a month, never oversold: your RAM is yours alone. And if you'd rather start without paying, there's a free Discord bot, no card needed.

Free tool: Discord colored text generator. Write on Discord in red, green or yellow, with backgrounds, bold and underline: the ansi block ready to paste, and the line for your bot.

Keep reading

All Discord bot guides, sorted →