Skip to main content
Events are the backbone of Discord bots. They notify your bot when something happens on Discord, like a message being sent, a member joining a server, or a reaction being added.

What are Events?

Events are asynchronous callbacks that Pycord dispatches when specific actions occur on Discord. Your bot can listen to these events and respond accordingly. The event system is implemented in discord/client.py:487 using the dispatch method, which triggers all registered event handlers.

Listening to Events

The most common way to listen to events is using the @bot.event decorator:
import discord

bot = discord.Bot()

@bot.event
async def on_ready():
    print(f"Logged in as {bot.user}")

@bot.event
async def on_message(message):
    if message.author.bot:
        return
    print(f"Message from {message.author}: {message.content}")

bot.run("TOKEN")
Using @bot.event replaces the default handler. Use @bot.listen to add additional handlers.

Common Events

Connection Events

Called when the bot has successfully connected to Discord and the internal cache is ready.
@bot.event
async def on_ready():
    print(f"Logged in as {bot.user.name}")
    print(f"Bot ID: {bot.user.id}")
    print(f"Connected to {len(bot.guilds)} guilds")
on_ready may be called multiple times if the bot reconnects. Don’t put one-time setup code here without proper guards.
Called when the bot establishes a connection to Discord (before cache is ready).
@bot.event
async def on_connect():
    print("Bot connected to Discord")
Called when the bot loses connection to Discord.
@bot.event
async def on_disconnect():
    print("Bot disconnected from Discord")

Message Events

Called when a message is sent in a channel the bot can see.
@bot.event
async def on_message(message):
    # Ignore messages from bots
    if message.author.bot:
        return
    
    # Respond to a specific message
    if message.content.startswith("hello"):
        await message.channel.send(f"Hello {message.author.mention}!")
    
    # Important: Process commands if you're using command extensions
    await bot.process_commands(message)
Requires Intent: guild_messages and/or dm_messagesRequires Privileged Intent: message_content (to read message content in guilds)
Called when a message is edited.
@bot.event
async def on_message_edit(before, after):
    if before.content != after.content:
        print(f"Message edited: {before.content} -> {after.content}")
Called when a message is deleted.
@bot.event
async def on_message_delete(message):
    print(f"Message deleted: {message.content}")

Member Events

Called when a member joins a guild.
@bot.event
async def on_member_join(member):
    channel = member.guild.system_channel
    if channel:
        await channel.send(f"Welcome {member.mention} to {member.guild.name}!")
Requires Privileged Intent: members
Called when a member leaves or is kicked from a guild.
@bot.event
async def on_member_remove(member):
    print(f"{member.name} left {member.guild.name}")
Requires Privileged Intent: members
Called when a member’s properties change (roles, nickname, etc.).
@bot.event
async def on_member_update(before, after):
    if before.nick != after.nick:
        print(f"Nickname changed: {before.nick} -> {after.nick}")
Requires Privileged Intent: members

Reaction Events

Called when a reaction is added to a message.
@bot.event
async def on_reaction_add(reaction, user):
    if user.bot:
        return
    
    print(f"{user.name} reacted with {reaction.emoji}")
    
    # Example: Delete message if thumbs down
    if str(reaction.emoji) == "👎":
        await reaction.message.delete()
Called when a reaction is removed from a message.
@bot.event
async def on_reaction_remove(reaction, user):
    print(f"{user.name} removed reaction {reaction.emoji}")

Guild Events

Called when the bot joins a guild.
@bot.event
async def on_guild_join(guild):
    print(f"Joined guild: {guild.name} (ID: {guild.id})")
    print(f"Member count: {guild.member_count}")
Called when the bot leaves or is removed from a guild.
@bot.event
async def on_guild_remove(guild):
    print(f"Left guild: {guild.name}")

Waiting for Events

Sometimes you need to wait for a specific event before continuing. Use bot.wait_for() for this:
@bot.command()
async def ask(ctx):
    await ctx.send("What's your favorite color?")
    
    def check(message):
        return message.author == ctx.author and message.channel == ctx.channel
    
    try:
        message = await bot.wait_for("message", check=check, timeout=30.0)
        await ctx.send(f"Your favorite color is {message.content}!")
    except asyncio.TimeoutError:
        await ctx.send("You took too long to respond!")

Wait for Reaction

@bot.command()
async def poll(ctx):
    message = await ctx.send("React with 👍 or 👎")
    await message.add_reaction("👍")
    await message.add_reaction("👎")
    
    def check(reaction, user):
        return user == ctx.author and str(reaction.emoji) in ["👍", "👎"]
    
    try:
        reaction, user = await bot.wait_for(
            "reaction_add",
            timeout=30.0,
            check=check
        )
        
        if str(reaction.emoji) == "👍":
            await ctx.send("You voted yes!")
        else:
            await ctx.send("You voted no!")
    except asyncio.TimeoutError:
        await ctx.send("Poll timed out!")

Event Error Handling

Handle errors that occur in event handlers:
@bot.event
async def on_error(event_method, *args, **kwargs):
    import traceback
    import sys
    
    print(f"Error in {event_method}:", file=sys.stderr)
    traceback.print_exc()
    
    # You could also log to a file or send to a logging channel
    error_channel = bot.get_channel(ERROR_CHANNEL_ID)
    if error_channel:
        await error_channel.send(f"Error in {event_method}")

Complete Event Reference

Here’s a comprehensive list of all available events:

Connection

  • on_connect - Bot connects to WebSocket
  • on_disconnect - Bot disconnects from WebSocket
  • on_ready - Bot is fully ready
  • on_resumed - Bot resumes session after disconnect

Messages

  • on_message - Message is sent
  • on_message_edit - Message is edited
  • on_message_delete - Message is deleted
  • on_bulk_message_delete - Multiple messages deleted
  • on_raw_message_edit - Message edited (raw data)
  • on_raw_message_delete - Message deleted (raw data)

Reactions

  • on_reaction_add - Reaction added
  • on_reaction_remove - Reaction removed
  • on_reaction_clear - All reactions cleared
  • on_reaction_clear_emoji - All reactions of emoji cleared
  • on_raw_reaction_add - Reaction added (raw data)
  • on_raw_reaction_remove - Reaction removed (raw data)

Members

  • on_member_join - Member joins guild
  • on_member_remove - Member leaves guild
  • on_member_update - Member profile updated
  • on_user_update - User account updated
  • on_member_ban - Member banned
  • on_member_unban - Member unbanned

Guilds

  • on_guild_join - Bot joins guild
  • on_guild_remove - Bot leaves guild
  • on_guild_update - Guild settings updated
  • on_guild_available - Guild becomes available
  • on_guild_unavailable - Guild becomes unavailable

Channels

  • on_guild_channel_create - Channel created
  • on_guild_channel_delete - Channel deleted
  • on_guild_channel_update - Channel updated
  • on_guild_channel_pins_update - Channel pins updated

Roles

  • on_guild_role_create - Role created
  • on_guild_role_delete - Role deleted
  • on_guild_role_update - Role updated

Voice

  • on_voice_state_update - Voice state changes

Interactions

  • on_interaction - Any interaction received
  • on_application_command - Slash command invoked
  • on_application_command_error - Slash command error

And Many More!

  • on_typing - User starts typing
  • on_invite_create / on_invite_delete - Invite management
  • on_webhooks_update - Webhook updated
  • on_presence_update - User presence changes
  • on_scheduled_event_create / update / delete - Event management

Complete Example

Here’s a complete bot with multiple event handlers:
import discord
from discord.ext import commands
import asyncio

intents = discord.Intents.default()
intents.members = True
intents.message_content = True

bot = commands.Bot(command_prefix="!", intents=intents)

@bot.event
async def on_ready():
    print(f"Bot is ready! Logged in as {bot.user}")
    print(f"Connected to {len(bot.guilds)} guilds")

@bot.event
async def on_message(message):
    # Ignore bot messages
    if message.author.bot:
        return
    
    # Log all messages
    print(f"[{message.guild.name}] {message.author}: {message.content}")
    
    # Process commands
    await bot.process_commands(message)

@bot.listen("on_message")
async def check_keywords(message):
    if "help" in message.content.lower():
        await message.add_reaction("❓")

@bot.event
async def on_member_join(member):
    channel = member.guild.system_channel
    if channel:
        embed = discord.Embed(
            title="Welcome!",
            description=f"{member.mention} joined {member.guild.name}",
            color=discord.Color.green()
        )
        await channel.send(embed=embed)

@bot.event
async def on_member_remove(member):
    print(f"{member.name} left {member.guild.name}")

@bot.event
async def on_error(event_method, *args, **kwargs):
    import traceback
    print(f"Error in {event_method}:")
    traceback.print_exc()

bot.run("TOKEN")

Best Practices

  1. Always check if the author is a bot to avoid infinite loops
  2. Use bot.listen() for multiple handlers instead of replacing with bot.event
  3. Call await bot.process_commands(message) in on_message if using command extensions
  4. Add error handling with on_error to catch issues
  5. Use wait_for() for interactive commands instead of polling
  6. Check required intents - some events need specific intents enabled

Build docs developers (and LLMs) love