Check that the bot has Manage Roles and its highest role sits above the target role. If the command does not run, enable Message Content Intent for a prefix command; if a member lookup fails, check the cache or fetch that member by ID.
Why Your Python Discord Bot Cannot Assign Roles
Diagnose Discord bot role assignment failures: check role hierarchy, permissions, member lookup, and prefix command intents with a working discord.py example.
// The short answer
In this guide · 5 sections
Running await member.add_roles(role) in discord.py is one of those things that looks completely straightforward until it silently fails or throws a 50013 error – even when you’ve given your bot Administrator privileges. Usually, tracking down the issue comes down to checking three specific layers: Discord's strict role hierarchy, your Developer Portal intents, and how your code handles errors.
| Error Mode | Primary Cause | Immediate Fix |
|---|---|---|
discord.Forbidden (50013) |
Target role outranks the bot's highest role. | Drag the bot's integration role higher in Server Settings. |
| Member Lookup Failure | Missing member in local cache or required gateway intent. | Check member lookup, then enable the intent or fetch the member by ID. |
AttributeError: 'NoneType' |
Missing role or member after a lookup, or a string ID passed to an integer lookup. | Validate the returned object and ID type before using it. |
The 50013 "Missing Permissions" Exception
The biggest roadblock with role assignment is a super common misconception about how Discord's permissions actually work. Giving your bot "Administrator" or "Manage Roles" is definitely required, sure, but it isn't a magic pass to do whatever it wants. Discord enforces a strict top-down hierarchy, meaning a bot can never assign, remove, or edit a role that sits higher than – or even equal to – its own highest role.
Whenever your code tries to cross that line, Discord's API shuts it down immediately and throws a discord.Forbidden (Error code: 50013) exception, killing the command on the spot.
Requirement 1: Server Role Hierarchy (The Primary Blocker)
To get around this hierarchy block, you’ll need to rearrange how the roles are stacked inside the server itself, and then add a quick safeguard in your code so it checks the order before trying anything.
Correcting Target-Role Placement
Head into Server Settings > Roles inside Discord and find your bot's integration role (that’s the one Discord creates automatically when the bot joins, usually with the same name as the bot). Grab that role and drag it upward until it sits physically above every single role you want the bot to manage.
Programmatically Checking Position in discord.py
Instead of just firing off add_roles blindly and letting a try/except block catch the crash, it's much cleaner to check the role positions beforehand. You can easily compare the target role's spot against the bot's highest role using ctx.me.top_role.position.
if role.position >= ctx.me.top_role.position:
await ctx.send("Execution blocked: Target role outranks my highest role.")
returnRequirement 2: Privileged Gateway Intents
If you aren't hitting a 50013 error, yet ctx.guild.get_member(user_id) keeps returning None, the problem is usually that the member isn't sitting in your bot's local cache. To keep that member cache populated, you'll want to turn on the Server Members Intent – though doing a direct API lookup with await ctx.guild.fetch_member(user_id) works too if you'd rather avoid caching.
- Head over to the Discord Developer Portal. 2.
Click on your application and open up the Bot tab on the left. 3.
Scroll down to Privileged Gateway Intents and toggle on Server Members Intent (so your member cache actually works) and Message Content Intent (so the bot can read the !assign prefix command below). 4. Make sure you also declare those intents right at the top of your Python script before you initialize the bot:
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.members = True
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)Writing a Resilient Role Assignment Command
A solid role assignment command shouldn't just assume everything will go right – it should validate the inputs, check the role hierarchy upfront, and catch API errors gracefully if things still go sideways. Here is a clean, minimal setup you can use to test your configuration:
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.members = True
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.command(name="assign")
@commands.has_permissions(manage_roles=True)
@commands.bot_has_permissions(manage_roles=True)
async def assign_role(ctx, member: discord.Member, role_id: int):
# 1. Grab the role object using its integer ID
role = ctx.guild.get_role(role_id)
if not role:
await ctx.send(f"Validation failed: Role ID {role_id} not found.")
return
# 2. Check the hierarchy before doing anything else
if role.position >= ctx.me.top_role.position:
await ctx.send(f"Hierarchy error: I cannot assign '{role.name}' because it outranks me.")
return
# 3. Assign the role with proper error handling
try:
await member.add_roles(role)
await ctx.send(f"Success: Assigned '{role.name}' to {member.display_name}.")
except discord.Forbidden:
await ctx.send("API Rejection: Missing 'Manage Roles' permission or hierarchy conflict.")
except discord.HTTPException as e:
await ctx.send(f"Network/API error during assignment: {e}")
bot.run('YOUR_BOT_TOKEN')Verifying the Fix
To make sure everything is actually working, bring a standard alt or test account into your server to run your tests. Testing on yourself as the server owner can give you false positives because owners automatically bypass a bunch of Discord's normal permission checks.
Run !assign @TestUser [Role_ID] in a test channel to see it in action. If the bot tells you it worked, yet the role never shows up on the test user, check for background conflicts – like another moderation bot instantly stripping the role away, or Discord's membership screening blocking roles until the user accepts the server rules.
Quick answers
Frequently asked questions
Why does my Discord bot fail to assign roles even though it has Administrator permissions?
Discord enforces a strict role hierarchy that even Administrator permissions can't bypass. Your bot simply cannot assign or modify any role sitting equal to or higher than its own top role in the server settings. To fix this, head into Server Settings and drag your bot's integration role above the target role so it can assign it properly.
What causes the discord.Forbidden 50013 error during role assignment?
That 50013 error pops up whenever the Discord API rejects your bot's attempt to manage a role because it doesn't have the right authority. This usually happens either because the bot is missing the Manage Roles permission or because it's trying to assign a role sitting higher than its own top role. Double-check its server-level permissions and drag the bot's role higher up in the list to clear it up.
Why does ctx.guild.get_member return None when assigning a role?
That method returns None whenever the member you're looking for isn't in your bot's local cache. If you need reliable member caching, turn on the Server Members Intent in the Developer Portal and set intents.members to True in your code. On the other hand, if you just need to look up a single known ID, use await ctx.guild.fetch_member(user_id) to grab them directly over HTTP instead of relying on the cache.
How can you programmatically prevent role hierarchy errors before calling add_roles?
Before you even send the API request, you can check role.position against ctx.me.top_role.position right inside your command logic. If the target role's position is greater than or equal to your bot's highest role, just stop the command early and send back a helpful message. Adding that quick check saves you from unhandled discord.Forbidden exceptions and unexpected crashes.
Can a Discord bot assign roles to the server owner?
No, a Discord bot can't change roles on the server owner under any circumstances. Discord hardcodes the server owner right at the very top of the permission hierarchy, no matter what roles are assigned to them. Trying to edit the owner's roles will always throw a 50013 error, so make sure you're testing your commands on a regular member account instead.
Why should you pass role IDs as integers instead of role names in discord.py?
Using numerical role IDs saves you from lookup headaches when roles get renamed, share duplicate names, or contain tricky Unicode characters. Passing an integer into ctx.guild.get_role gives you a direct cache lookup that grabs the exact role object right away. Just remember that if you're pulling the ID from user input or a config file, you'll want to cast it to an integer first so you don't run into type mismatches.
Why does a role appear on a user and then immediately disappear?
When you see that happen, it means your bot actually assigned the role fine, but another system or server rule immediately took it back off. Take a look at your server audit logs to see if another moderation bot, an auto-role loop, or a verification setup is overriding manual role changes. Also, keep in mind that incomplete membership screening rules can delay or undo role updates for newly joined members.
Something wrong or out of date?
Tools change fast. Tell the editor what no longer works.