{"slug":"how-to-make-a-discord-bot-in-python","article":{"slug":"how-to-make-a-discord-bot-in-python","name":"How to make a Discord bot in Python","summary":"Learn how to make a Discord bot in Python. Explore different methods, tips, real-world applications, and how to debug common errors.","metaTitle":"How to make a Discord bot in Python | Replit","metaDescription":"Learn how to make a Discord bot in Python. Explore different methods, tips, real-world applications, and how to debug common errors.","category":"coding-tutorials","lang":"python","thumbnail":"https://discover-articles.replit.app/api/storage/public-objects/article-thumbnails/how-to-make-a-discord-bot-in-python.png","thumbnailVariants":[640,1280],"ogImage":"https://discover-articles.replit.app/api/storage/public-objects/article-og/how-to-make-a-discord-bot-in-python-og.png","publishedOn":"Tue Apr 14 2026 01:01:55 GMT+0000 (Coordinated Universal Time)","updatedOn":"Tue Apr 14 2026 01:00:30 GMT+0000 (Coordinated Universal Time)","blocks":[{"type":"p","html":"A Discord bot can automate tasks, engage communities, and add custom features to your server. Python, with its simple syntax and powerful libraries, is an excellent choice to build one."},{"type":"p","html":"In this article, you'll learn essential techniques to get started, from initial setup to deployment. You'll get practical tips, explore real-world applications, and receive debugging advice to ensure your bot runs smoothly."},{"type":"h2","level":2,"text":"Basic setup with discord.py","slug":"basic-setup-with-discordpy"},{"type":"code","code":"import discord\nfrom discord.ext import commands\n\nbot = commands.Bot(command_prefix='!', intents=discord.Intents.default())\n\n@bot.event\nasync def on_ready():\n    print(f'{bot.user} has connected to Discord!')\n\nbot.run('YOUR_TOKEN_HERE')","output":"MyBot#1234 has connected to Discord!","lang":"python"},{"type":"p","html":"This setup initializes your bot using the <code>commands.Bot</code> class. Two parameters are key to its function:"},{"type":"ul","items":["<code>command_prefix='!'</code>: This defines the character your bot looks for to identify a command. Any message starting with <code>'!'</code> will be processed.","<code>intents=discord.Intents.default()</code>: This is essential. Intents declare which events your bot needs from Discord's servers. The default set is a safe starting point, granting access to common events like messages without requiring special permissions."]},{"type":"p","html":"The <code>@bot.event</code> decorator registers the asynchronous <code>on_ready</code> function to confirm a successful connection, and <code>bot.run()</code> starts the bot using your unique token."},{"type":"h2","level":2,"text":"Core bot functionality","slug":"core-bot-functionality"},{"type":"p","html":"With the basic connection established, you can now teach your bot to interact with users, handle commands, and present information in visually appealing ways."},{"type":"h3","level":3,"text":"Responding to user messages","slug":"responding-to-user-messages"},{"type":"code","code":"@bot.event\nasync def on_message(message):\n    if message.author == bot.user:\n        return\n        \n    if 'hello' in message.content.lower():\n        await message.channel.send('Hello there!')\n    \n    await bot.process_commands(message)","output":"User: hello\nBot: Hello there!","lang":"python"},{"type":"p","html":"The <code>on_message</code> event triggers for every message your bot can see. The first check—<code>if message.author == bot.user:</code>—is a critical safeguard. It prevents the bot from replying to its own messages and getting stuck in an infinite loop."},{"type":"p","html":"After this check, the bot looks for 'hello' in the message content to send a reply. The final line, <code>await bot.process_commands(message)</code>, is vital. It ensures your bot still processes regular commands, so this custom response logic doesn't interfere with your command prefix functionality."},{"type":"h3","level":3,"text":"Creating custom commands with <code>@bot.command()</code>","slug":"creating-custom-commands-with-botcommand"},{"type":"code","code":"@bot.command(name='ping')\nasync def ping_command(ctx):\n    await ctx.send(f'Pong! Latency: {round(bot.latency * 1000)}ms')\n\n@bot.command()\nasync def echo(ctx, *, message):\n    await ctx.send(message)","output":"User: !ping\nBot: Pong! Latency: 42ms\nUser: !echo Hello world\nBot: Hello world","lang":"python"},{"type":"p","html":"The <code>@bot.command()</code> decorator is the standard way to create commands. The function name becomes the command's trigger, though you can override it, as seen with <code>name='ping'</code>. Every command function receives a <code>ctx</code> (context) object, which holds details like the channel and author and lets you send replies with <code>ctx.send()</code>."},{"type":"ul","items":["The <code>ping</code> command is a simple diagnostic tool that replies with the bot's current latency to Discord's servers.","In the <code>echo</code> command, the asterisk in <code>*, message</code> is a keyword-only argument that captures all text after the command as a single string."]},{"type":"h3","level":3,"text":"Working with rich embeds for better visuals","slug":"working-with-rich-embeds-for-better-visuals"},{"type":"code","code":"@bot.command()\nasync def info(ctx):\n    embed = discord.Embed(title=\"Bot Info\", description=\"A cool Discord bot\", color=0x00ff00)\n    embed.add_field(name=\"Author\", value=\"Your Name\", inline=False)\n    embed.add_field(name=\"Version\", value=\"1.0\", inline=True)\n    await ctx.send(embed=embed)","output":"[An embedded message appears with the title \"Bot Info\", description \"A cool Discord bot\", and fields for Author and Version]","lang":"python"},{"type":"p","html":"Embeds are a fantastic way to present information cleanly. Instead of a wall of text, you can create structured, colorful messages. You start by creating a <code>discord.Embed</code> object, where you can set the main <code>title</code>, <code>description</code>, and <code>color</code>."},{"type":"ul","items":["Use <code>embed.add_field()</code> to add organized sections of data.","The <code>inline</code> parameter controls layout. Setting <code>inline=True</code> allows fields to sit side-by-side, while <code>inline=False</code> ensures a field takes up the full width."]},{"type":"p","html":"Finally, you send the completed object with <code>ctx.send(embed=embed)</code>."},{"type":"h2","level":2,"text":"Advanced bot development","slug":"advanced-bot-development"},{"type":"p","html":"With the core functionality in place, you can now focus on making your bot more robust and scalable by handling events, organizing code, and managing errors."},{"type":"h3","level":3,"text":"Handling different Discord events","slug":"handling-different-discord-events"},{"type":"code","code":"@bot.event\nasync def on_member_join(member):\n    channel = member.guild.system_channel\n    if channel:\n        await channel.send(f'Welcome {member.mention} to the server!')\n\n@bot.event\nasync def on_reaction_add(reaction, user):\n    if user != bot.user and str(reaction.emoji) == '👍':\n        await reaction.message.channel.send(f'{user.name} gave a thumbs up!')","output":"[When a new member joins]\nBot: Welcome @NewUser to the server!\n\n[When someone adds a 👍 reaction]\nBot: JohnDoe gave a thumbs up!","lang":"python"},{"type":"p","html":"Your bot can do more than just respond to commands. The <code>@bot.event</code> decorator lets you listen for specific server activities, making your bot feel more integrated and alive. This allows you to create automated responses to user actions that aren't prefixed commands, though proper <a href=\"https://blog.replit.com/finding-and-solving-memory-leaks\">memory management</a> becomes crucial for bots handling many events."},{"type":"ul","items":["The <code>on_member_join</code> event triggers when a new user joins. The code uses the <code>member</code> object to get their details and sends a welcome message to the server's designated system channel.","<code>on_reaction_add</code> fires whenever a reaction is added to a message. The example checks if the emoji is <code>'👍'</code> and ensures the bot isn't responding to its own actions before sending a confirmation."]},{"type":"h3","level":3,"text":"Organizing code with <code>Cogs</code> and extensions","slug":"organizing-code-with-cogs-and-extensions"},{"type":"code","code":"class Greetings(commands.Cog):\n    def __init__(self, bot):\n        self.bot = bot\n    \n    @commands.command()\n    async def hello(self, ctx):\n        await ctx.send(f'Hello, {ctx.author.name}!')\n\nasync def setup(bot):\n    await bot.add_cog(Greetings(bot))","output":"[In main file after adding]\nawait bot.load_extension('greetings')\n\nUser: !hello\nBot: Hello, JohnDoe!","lang":"python"},{"type":"p","html":"As your bot gets more complex, Cogs help you keep your code organized. A Cog is a Python class that groups related commands and event listeners together. By placing each Cog in a separate file, known as an extension, you can keep your main bot file clean and manageable."},{"type":"ul","items":["A class becomes a Cog by inheriting from <code>commands.Cog</code>. All its commands and listeners are bundled within it.","Each extension file must contain a <code>setup</code> function. This is what discord.py calls to register the class with your bot using <code>bot.add_cog()</code>.","You activate everything in the file from your main script by calling <code>bot.load_extension()</code>."]},{"type":"h3","level":3,"text":"Implementing error handling for robust bots","slug":"implementing-error-handling-for-robust-bots"},{"type":"code","code":"import logging\n\nlogging.basicConfig(level=logging.INFO)\n\n@bot.event\nasync def on_command_error(ctx, error):\n    if isinstance(error, commands.MissingRequiredArgument):\n        await ctx.send('Please provide all required arguments.')\n    elif isinstance(error, commands.CommandNotFound):\n        await ctx.send('Command not found.')\n    else:\n        logging.error(f'Error: {str(error)}')","output":"User: !echo\nBot: Please provide all required arguments.\n\nUser: !unknown\nBot: Command not found.\n\n[In console/log file]\nERROR:root:Error: Something went wrong","lang":"python"},{"type":"p","html":"A robust bot gracefully handles mistakes. The <code>on_command_error</code> event acts as a global safety net, catching any errors that occur when a command is run. This prevents your bot from crashing and lets you give users helpful feedback, similar to how <a href=\"https://blog.replit.com/introducing-plan-mode-a-safer-way-to-vibe-code\">plan mode</a> provides a safer development approach. Understanding handling multiple exceptions in Python is crucial for building robust error handling systems."},{"type":"ul","items":["The code uses <code>isinstance()</code> to check the specific type of error. It sends a friendly message for common issues like <code>commands.MissingRequiredArgument</code> or <code>commands.CommandNotFound</code>.","For any other unexpected errors, the <code>else</code> block logs the full error details using <code>logging.error()</code>. This keeps your server chat clean while giving you the information you need to debug."]},{"type":"h2","level":2,"text":"Move faster with Replit","slug":"move-faster-with-replit"},{"type":"p","html":"Replit is an AI-powered development platform that comes with all Python <a href=\"https://blog.replit.com/system-dependencies-python\">dependencies pre-installed</a>, so you can skip setup and start coding instantly. You don't need to worry about managing environments or installations."},{"type":"p","html":"Instead of piecing together techniques, you can build a complete application. This is where <a href=\"https://replit.com/agent4\">Agent 4</a> comes in. Describe the bot you want to build, and the Agent handles everything from writing the code to connecting APIs and deploying it."},{"type":"ul","items":["A welcome bot that greets new members with a custom embedded message, triggered by the <code>on_member_join</code> event.","A reaction-role tool that automatically assigns roles when users react to a message, perfect for server verification or channel access.","A custom command bot that fetches data from an external API—like weather or game stats—and displays it in a clean, formatted embed."]},{"type":"p","html":"Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser."},{"type":"h2","level":2,"text":"Common errors and challenges","slug":"common-errors-and-challenges"},{"type":"p","html":"Building a bot involves some common hurdles, but most issues have straightforward fixes once you know where to look."},{"type":"h3","level":3,"text":"Fixing the <code>on_message</code> event when commands stop working","slug":"fixing-the-on_message-event-when-commands-stop-working"},{"type":"p","html":"If your commands suddenly stop working after you've added an <code>on_message</code> event, it's because you've overridden the bot's default message handling. The <code>discord.py</code> library uses its own internal <code>on_message</code> to listen for and dispatch commands. When you define your own, that internal process is ignored."},{"type":"p","html":"The fix is simple: you just need to tell your bot to continue looking for commands after your custom logic runs. Including <code>await bot.process_commands(message)</code> at the end of your <code>on_message</code> function ensures that messages are passed along for command processing, allowing both your event logic and your commands to work together."},{"type":"h3","level":3,"text":"Troubleshooting permission errors with <code>discord.py</code>","slug":"troubleshooting-permission-errors-with-discordpy"},{"type":"p","html":"Permission-related errors, often appearing as <code>discord.Forbidden</code>, usually stem from one of two areas: your bot's server role or its declared intents."},{"type":"ul","items":["<strong>Server Permissions:</strong> Your bot is like any other user on your server and is governed by roles. If your bot needs to kick members or create channels, its role must have those specific permissions enabled in your server's settings under \"Roles.\"","<strong>Bot Intents:</strong> Intents are subscriptions to specific events from Discord. If you want your bot to access message content or track when members join, you must enable privileged intents in both your code—for example, <code>discord.Intents.all()</code>—and in your bot's settings on the Discord Developer Portal."]},{"type":"h3","level":3,"text":"Resolving issues with <code>async</code>/<code>await</code> in command functions","slug":"resolving-issues-with-async-await-in-command-functions"},{"type":"p","html":"Many functions in <code>discord.py</code> are coroutines, which are special functions that don't run immediately. Instead of returning a value directly, they return an object that must be run by an event loop. This is where the <code>async</code> and <code>await</code> keywords come into play."},{"type":"p","html":"If a command seems to do nothing—no message sent, no error—you've likely forgotten to use <code>await</code>. For example, writing <code>ctx.send(\"hello\")</code> won't actually send the message. You must write <code>await ctx.send(\"hello\")</code> to pause your function, execute the send operation, and wait for it to complete. Forgetting this is a common mistake that causes code to fail silently."},{"type":"h3","level":3,"text":"Fixing the <code>on_message</code> event when commands stop working","slug":"fixing-the-on_message-event-when-commands-stop-working-2"},{"type":"p","html":"When you define your own <code>on_message</code> event, you take full control over how the bot handles incoming messages. If your prefixed commands suddenly stop working, it’s because the bot is no longer processing them. The code below illustrates this common pitfall."},{"type":"code","code":"@bot.event\nasync def on_message(message):\n    if message.author == bot.user:\n        return\n        \n    if 'hello' in message.content.lower():\n        await message.channel.send('Hello there!')\n    \n    # Commands won't work without this line","output":null,"lang":"python"},{"type":"p","html":"By defining a custom <code>on_message</code> event, you've taken over message handling. The function runs its logic but never tells the bot to check for commands, effectively ignoring them. The corrected code below adds the necessary line."},{"type":"code","code":"@bot.event\nasync def on_message(message):\n    if message.author == bot.user:\n        return\n        \n    if 'hello' in message.content.lower():\n        await message.channel.send('Hello there!')\n    \n    await bot.process_commands(message)","output":null,"lang":"python"},{"type":"p","html":"The fix is simple: add <code>await bot.process_commands(message)</code> to the end of your <code>on_message</code> function. By default, defining this event overrides the bot's ability to process commands. This line hands control back, ensuring your prefixed commands still run after your custom message logic. You'll need this whenever you want your bot to both react to specific keywords and respond to commands, as it's easy to forget this final step."},{"type":"h3","level":3,"text":"Troubleshooting permission errors with <code>discord.py</code>","slug":"troubleshooting-permission-errors-with-discordpy-2"},{"type":"p","html":"Permission errors, often appearing as <code>discord.Forbidden</code>, happen when your bot attempts an action without the right authorization. This is especially common with administrative commands, like a <code>kick</code> function, even when your code seems correct. The following example demonstrates this scenario."},{"type":"code","code":"@bot.command()\nasync def kick(ctx, member: discord.Member, *, reason=None):\n    await member.kick(reason=reason)\n    await ctx.send(f'Kicked {member.display_name}')","output":null,"lang":"python"},{"type":"p","html":"The <code>kick</code> command itself is valid, but it requests a privileged action. If the bot's server role doesn't have 'Kick Members' permission, Discord will forbid the action, causing an error. The solution involves a two-part check."},{"type":"code","code":"@bot.command()\nasync def kick(ctx, member: discord.Member, *, reason=None):\n    try:\n        await member.kick(reason=reason)\n        await ctx.send(f'Kicked {member.display_name}')\n    except discord.Forbidden:\n        await ctx.send(\"I don't have permission to kick members.\")","output":null,"lang":"python"},{"type":"p","html":"The solution is to wrap the privileged action, <code>await member.kick()</code>, in a <code>try...except</code> block. This lets your bot attempt the command and handle failure gracefully instead of crashing. Learning the fundamentals of try and except in Python will help you implement proper error handling throughout your bot. You should watch for this error whenever your bot performs an action that requires special permissions, such as banning users or managing roles."},{"type":"ul","items":["<strong>The Fix:</strong> Catch the specific <code>discord.Forbidden</code> error and send a user-friendly message explaining that the bot lacks permission."]},{"type":"h3","level":3,"text":"Resolving issues with <code>async</code>/<code>await</code> in command functions","slug":"resolving-issues-with-async-await-in-command-functions-2"},{"type":"p","html":"Many <code>discord.py</code> functions are asynchronous, meaning they won't run unless you use the <code>await</code> keyword. Forgetting it is a common mistake that causes commands to fail silently without any errors. The code below shows a typical example of this problem."},{"type":"code","code":"@bot.command()\ndef say_hello(ctx):\n    ctx.send(\"Hello!\")","output":null,"lang":"python"},{"type":"p","html":"Because the <code>ctx.send()</code> coroutine is never awaited, the command appears to run successfully but produces no output. The bot simply does nothing. The corrected code below shows how to properly execute the asynchronous function."},{"type":"code","code":"@bot.command()\nasync def say_hello(ctx):\n    await ctx.send(\"Hello!\")","output":null,"lang":"python"},{"type":"p","html":"The solution is to make the command function asynchronous by using <code>async def</code> and then calling the action with <code>await</code>. Functions like <code>ctx.send()</code> are coroutines—they don't run on their own. Forgetting <code>await</code> means the send operation is prepared but never actually executed, so your command does nothing. You'll need to use <code>await</code> for any <code>discord.py</code> function that performs an action, like sending messages or fetching user data."},{"type":"h2","level":2,"text":"Real-world applications","slug":"real-world-applications"},{"type":"p","html":"With the fundamentals and common errors covered, you can now build practical applications that automate and engage your community."},{"type":"h3","level":3,"text":"Creating a role reaction system with <code>discord.py</code>","slug":"creating-a-role-reaction-system-with-discordpy"},{"type":"p","html":"A reaction-role system automates server management by letting users assign themselves roles, a feature you can build by handling the <code>on_raw_reaction_add</code> event. This approach to <a href=\"https://blog.replit.com/building-my-first-slack-bot\">building chat bots</a> demonstrates similar automation patterns across different platforms."},{"type":"code","code":"@bot.event\nasync def on_raw_reaction_add(payload):\n    if payload.message_id != 123456789:  # Replace with your message ID\n        return\n        \n    guild = bot.get_guild(payload.guild_id)\n    member = guild.get_member(payload.user_id)\n    \n    if str(payload.emoji) == \"🔴\":\n        red_role = discord.utils.get(guild.roles, name=\"Red Team\")\n        await member.add_roles(red_role)\n    elif str(payload.emoji) == \"🔵\":\n        blue_role = discord.utils.get(guild.roles, name=\"Blue Team\")\n        await member.add_roles(blue_role)","output":null,"lang":"python"},{"type":"p","html":"This code uses the <code>on_raw_reaction_add</code> event, which is powerful because it captures reactions on any message, even those not in the bot's cache. The function first checks if the reaction occurred on a specific message by comparing the <code>payload.message_id</code>. If it's the correct message, the bot takes action based on the emoji."},{"type":"ul","items":["It fetches the server and the specific member who reacted using IDs from the <code>payload</code>.","It checks which emoji was used, such as <code>\"🔴\"</code> or <code>\"🔵\"</code>.","Finally, it finds a server role by its name—like \"Red Team\"—and assigns it to the member using <code>member.add_roles()</code>."]},{"type":"h3","level":3,"text":"Implementing scheduled announcements with <code>asyncio</code>","slug":"implementing-scheduled-announcements-with-asyncio"},{"type":"p","html":"You can create automated, recurring messages by using the <code>discord.py</code> <code>tasks</code> extension, which leverages <code>asyncio</code> to run functions like sending announcements on a set schedule."},{"type":"code","code":"import asyncio\nimport datetime\n\n@tasks.loop(hours=24)\nasync def daily_announcement():\n    channel = bot.get_channel(987654321)  # Announcement channel ID\n    weekday = datetime.datetime.now().strftime('%A')\n    \n    if weekday == 'Monday':\n        await channel.send(\"It's Monday! Weekly team meeting at 10 AM.\")\n    elif weekday == 'Friday':\n        await channel.send(\"Happy Friday! Don't forget to submit your weekly reports.\")\n    else:\n        await channel.send(f\"Good morning everyone! It's {weekday}.\")\n\n@bot.event\nasync def on_ready():\n    print(f'{bot.user} has connected to Discord!')\n    daily_announcement.start()","output":null,"lang":"python"},{"type":"p","html":"The <code>@tasks.loop</code> decorator is a powerful tool for creating scheduled events without complex <code>asyncio</code> boilerplate. It wraps the <code>daily_announcement</code> function, setting it to run on a fixed interval. For more granular timing control, you might also need to understand <a href=\"https://replit.com/discover/how-to-use-sleep-in-python\">using sleep in Python</a> for custom delays. The function itself is straightforward—it grabs a channel, gets the current day using <code>datetime</code>, and sends a message based on conditional logic."},{"type":"ul","items":["Starting the task within <code>on_ready</code> using <code>daily_announcement.start()</code> is critical.","This guarantees the bot is fully initialized before the loop attempts to fetch a channel and send its first message, preventing startup errors."]},{"type":"h2","level":2,"text":"Get started with Replit","slug":"get-started-with-replit"},{"type":"p","html":"Now, turn these techniques into a working tool. Describe what you want to <a href=\"https://replit.com/agent4\">Replit Agent</a>, like “a bot that assigns roles based on emoji reactions” or “a bot that sends scheduled daily announcements.”"},{"type":"p","html":"The Agent will write the code, test for errors, and deploy your application for you. <a href=\"https://replit.com\">Start building with Replit</a>."}],"toc":[{"slug":"basic-setup-with-discordpy","text":"Basic setup with discord.py"},{"slug":"core-bot-functionality","text":"Core bot functionality"},{"slug":"advanced-bot-development","text":"Advanced bot development"},{"slug":"move-faster-with-replit","text":"Move faster with Replit"},{"slug":"common-errors-and-challenges","text":"Common errors and challenges"},{"slug":"real-world-applications","text":"Real-world applications"},{"slug":"get-started-with-replit","text":"Get started with Replit"}],"faq":[]}}