Use @plugin.on_event(name) for raw Discord events. Twenty event types are dispatched to plugins:
| Event | Payload highlights |
|---|---|
| message_create | message_id, channel_id, author_id, author_bot, content, created_at |
| message_edit | message_id, channel_id, content |
| message_delete | message_id, channel_id |
| member_join | user_id, username, guild_id |
| member_leave | user_id, username, guild_id |
| member_update | user_id, roles |
| reaction_add | message_id, channel_id, user_id, emoji |
| reaction_remove | message_id, channel_id, user_id, emoji |
| voice_state_update | user_id, before_channel_id, after_channel_id, self_mute, self_deaf |
| interaction_create | interaction_id, interaction_type, command_name, options, custom_id, values, modal_values |
| channel_create | channel_id, name, type |
| channel_delete | channel_id, name |
| channel_update | channel_id, name |
| role_create | role_id, name |
| role_delete | role_id, name |
| role_update | role_id, name |
| reaction_clear | message_id, channel_id (all reactions removed at once) |
| thread_create | id, name |
| guild_join | id, name (the bot joined the server) |
| guild_remove | id (the bot left / was removed) |
Field availability can differ slightly by delivery path (for example author_bot), so read optional fields defensively with event.get(...).
Note: for slash commands, button clicks, and modal submits use the dedicated decorators below — they're easier than picking through interaction_create.
Message text is capability-gated: without events:message_content the whole payload is reduced to IDs and timestamps — content, author fields and attachments are omitted (interaction fields like options and custom_id are always preserved). The events still fire either way. The 16 core payloads also ship typed shapes you can import for autocomplete — from yourbot_sdk.events import MessageCreate, MemberJoin, … — while the last four (reaction_clear, thread_create, guild_join, guild_remove) are plain dicts.
Delivery guarantees
- Delivered events arrive at least once: your handler can be called twice for the same event, so make side effects idempotent or guard with
ctx.ephemeral.dedup(…). Under extreme load the platform sheds gateway events rather than buffering them, so don't build flows that assume every Discord event reaches you. - If a handler raises, the event is marked failed and is not retried — the full traceback lands in your plugin logs. (If the whole worker dies mid-event, the event is re-delivered, up to 5 attempts — one more reason handlers must be idempotent.)
- 5 consecutive crashes within 5 minutes quarantines the plugin (see the Production tab).
- Handlers for the same server run on a small thread pool and can execute concurrently. Module-level state shared across handlers must be thread-safe (use
threading.Lock).