Quick answer: When a Discord.py bot overrides on_message, it can replace the command extension’s normal dispatch. Keep custom message logic short and call await bot.process_commands(message), or register an additional on_message listener so commands continue to run.

Discord bot commands often stop working after adding an on_message event because the custom event handler replaces the default command dispatch path. When you override on_message, Discord.py receives the message, runs your event handler, and does not automatically pass that message to the command extension.
The usual fix is to call await bot.process_commands(message) inside the on_message handler after your custom message logic. That call tells the commands extension to inspect the same message for prefixes and registered commands.
This issue is not usually caused by the command function itself. A command such as !ping can be defined correctly and still never run if on_message consumes the message without forwarding it to the command processor.
Think of on_message as the front door for message events. Once you define your own front-door behavior, you are responsible for handing normal command messages to the command system. That is why the location of process_commands() matters.
The official Discord.py FAQ for on_message command issues, commands extension documentation, on_message event documentation, intents documentation, and process_commands documentation are the primary references.
Create The Bot With Message Content Intent
For prefix commands that read message text, make sure the bot is created with message content intent and that the same intent is enabled for the bot in the Discord developer portal.
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
If the bot cannot read message content, it cannot see command prefixes in normal messages. Fix intents before debugging command decorators.
Call process_commands In on_message
When you override on_message, call bot.process_commands(message) after custom handling so registered commands still run.
@bot.event
async def on_message(message):
if message.author.bot:
return
if "hello bot" in message.content.lower():
await message.channel.send("Hello!")
await bot.process_commands(message)
The early return for bot authors is fine because you normally do not want bot messages to trigger commands. For user messages, the final line keeps command dispatch active.

Keep Commands Registered Normally
Your command functions should still be registered with @bot.command(). The on_message handler does not replace command registration; it only decides whether messages reach that command system.
@bot.command()
async def ping(ctx):
await ctx.send("pong")
@bot.command()
async def repeat(ctx, *, text):
await ctx.send(text)
If !ping works before adding on_message and stops afterward, the command definition is probably not the issue. The message is not reaching the command processor.
Use bot.listen For Extra Message Watchers
If you only need to observe messages and do not need to replace the event, use bot.listen(). A listener can run alongside command processing without requiring a manual process_commands() call.
@bot.listen("on_message")
async def watch_for_thanks(message):
if message.author.bot:
return
if "thanks" in message.content.lower():
await message.channel.send("Glad to help.")
This is often cleaner for lightweight reactions, logging, moderation checks, and analytics. Use on_message only when you truly need to override the event.
Avoid Returning Before Command Processing
Returns inside on_message can accidentally skip command processing. Keep conditions narrow, and call process_commands() for every user message that should still support commands.
@bot.event
async def on_message(message):
if message.author.bot:
return
blocked_words = {"spam", "scam"}
if any(word in message.content.lower() for word in blocked_words):
await message.delete()
return
await bot.process_commands(message)
In this pattern, deleted messages do not continue to command processing. All other user messages do. That keeps moderation behavior intentional.

Add A Command Error Logger
When commands still do not respond, add a small error handler so you can distinguish dispatch problems from command exceptions.
from discord.ext import commands
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
return
await ctx.send(f"Command failed: {error.__class__.__name__}")
If this handler never runs for a command message, the message probably did not reach the command extension. Check on_message, intents, and the prefix.
Start The Bot Without Hard-Coding The Token
Read the token from the runtime environment or your deployment secret store. Do not paste a real bot token into source code or examples.
Restart the bot after changing event handlers or intents. A running process keeps the old code until it is restarted, so changes to on_message will not take effect in an already-running process.
If the bot runs under a process manager, container, or hosting panel, restart that process rather than only saving the file. Then send a simple command in a server channel where the bot has permission to read and respond.

Fix Checklist
First, confirm that message content intent is enabled in code and in the Discord developer portal. Then verify the command prefix and make sure the command is registered with @bot.command().
Next, inspect on_message. If it exists, make sure user messages eventually reach await bot.process_commands(message). If you only need passive message observation, replace the override with @bot.listen("on_message").
Finally, restart the bot and test one simple command such as !ping. Once that works, add the custom message logic back one condition at a time so the exact blocking branch is easy to find.
Why Commands Stop
The command extension needs each message to reach its processor. A custom on_message event handler that returns without calling process_commands() consumes the event from the command system, so prefixes and command callbacks appear to stop working.
import discord
from discord.ext import commands
intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_message(message):
if message.author.bot:
return
print(message.content)
await bot.process_commands(message)
Use process_commands() After Custom Work
Call process_commands() after logging, filtering, or other lightweight custom behavior. Keep the call reachable for messages that should be commands. If a message is intentionally excluded, document that policy and do not expect a command to run for it.
@bot.command()
async def ping(ctx):
await ctx.send("pong")
# A message containing !ping now reaches the command callback.

Prefer An Additional Listener
bot.listen(‘on_message’) registers another listener without replacing the default event handler supplied by the commands extension. This is often safer when the custom handler only needs to observe messages.
@bot.listen("on_message")
async def audit_messages(message):
if not message.author.bot:
print("seen:", message.id)
Do Not Block The Event Loop
Network calls, sleeps, or CPU-heavy work inside on_message delay every event, including commands. Use awaitable library calls, asyncio.sleep(), and an executor or worker for CPU-bound work. Also enable the message content intent where the bot’s command configuration requires it.
import asyncio
@bot.listen("on_message")
async def delayed_audit(message):
await asyncio.sleep(0)
print("handled", message.id)
discord.py’s official process_commands() reference documents the command dispatch call. Keep the bot’s event and intent configuration aligned with the current Discord API requirements.
For related event-loop behavior, compare running event loops, async-library detection, and thread locks before adding work to a message handler.
Frequently Asked Questions
Why do Discord.py commands stop after adding on_message?
Overriding on_message can prevent the bot from dispatching messages to its command processor.
How do I fix on_message commands in Discord.py?
Call await bot.process_commands(message) at the end of on_message after the custom message logic.
Should I use an on_message listener instead?
Use bot.listen(‘on_message’) when you want an additional handler without replacing the framework’s default event handler.
Can a blocking on_message handler break commands?
Yes. Blocking work delays the event loop; use async-compatible I/O or move CPU-heavy work out of the handler.