← Documentation
/ Developer Guide
Build a plugin
The full SDK reference for plugin developers — sandboxed Python, shipped as a zip, distributed through the marketplace.
SDK v0.5.1 · Plugins are sandboxed Python processes. Write a handler, declare your capabilities, ship a zip — we run it in a locked-down Docker container and stream Discord events to it over JSON-RPC.
Reference — ctx attributes
| Attribute | Type |
|---|---|
| ctx.server_id | str — Discord server (guild) ID |
| ctx.plugin_id | str — your plugin's id |
| ctx.version | str — installed version string |
| ctx.capabilities | set[str] — capabilities the user approved |
| ctx.discord | Discord REST surface |
| ctx.interaction | Interaction response surface |
| ctx.kv | Per-server KV store |
| ctx.sql | Sandboxed SQL (capability) |
| ctx.ephemeral | Redis-backed counters / cooldowns / flags |
| ctx.http | Outbound HTTP via proxy |
| ctx.metrics | Time-series metrics |
| ctx.log(message, *, level, tags, **extra) | Structured plugin log |
| ctx.has_capability(name) | bool — capability check |
ctx.discord
| Method | Capability |
|---|---|
| send_message(*, channel_id, content="", embeds=None, components=None, files=None) | discord:send_message |
| edit_message(*, channel_id, message_id, content=None, embeds=None) | discord:edit_message |
| delete_message(*, channel_id, message_id) | discord:delete_message |
| bulk_delete_messages(*, channel_id, message_ids) | discord:delete_message |
| add_reaction(*, channel_id, message_id, emoji) | discord:add_reaction |
| pin_message(*, channel_id, message_id) | discord:edit_message |
| unpin_message(*, channel_id, message_id) | discord:edit_message |
| get_messages(*, channel_id, limit=50, before=None, after=None) | discord:read |
| get_guild() | discord:read |
| get_channel(*, channel_id) | discord:read |
| list_channels() | discord:read |
| get_member(*, user_id) | discord:read |
| list_members(*, role_id=None, limit=100, after=None) | discord:read |
| search_members(query, *, limit=25) | discord:read |
| list_roles() | discord:read |
| create_channel(*, name, channel_type=0, category_id=None, topic=None, user_limit=None) | discord:manage_channels |
| edit_channel(*, channel_id, name=None, topic=None, user_limit=None) | discord:manage_channels |
| delete_channel(*, channel_id) | discord:manage_channels |
| set_channel_permissions(*, channel_id, target_id, allow="0", deny="0", target_type=0) | discord:manage_channels |
| delete_channel_permission(*, channel_id, target_id) | discord:manage_channels |
| create_thread(*, channel_id, name, thread_type=11, auto_archive_duration=1440) | discord:manage_channels |
| edit_thread(*, thread_id, archived=None, locked=None, name=None, auto_archive_duration=None) | discord:manage_channels |
| timeout_member(*, user_id, duration_seconds, reason="") | discord:moderate_members |
| set_nickname(*, user_id, nickname=None) | discord:moderate_members |
| kick_member(*, user_id, reason="") | discord:kick_members |
| ban_member(*, user_id, reason="", delete_message_seconds=0) | discord:ban_members |
| unban_member(*, user_id) | discord:ban_members |
| add_role(*, user_id, role_id, reason="") | discord:manage_roles |
| remove_role(*, user_id, role_id, reason="") | discord:manage_roles |
| add_role_bulk / remove_role_bulk / timeout_bulk / kick_bulk | (matches single-action capability) |
| create_webhook(*, channel_id, name) | discord:manage_webhooks |
| execute_webhook(*, webhook_id, webhook_token, content="", embeds=None, username=None, avatar_url=None) | discord:send_message |
| delete_webhook(*, webhook_id) | discord:manage_webhooks |
ctx.interaction
| Method | Use |
|---|---|
| respond(*, content="", embeds=None, components=None, ephemeral=False, allowed_mentions=None) | First reply (within 3s of receiving the interaction). allowed_mentions mirrors Discord's API field — e.g. {"parse": []} suppresses all pings. |
| defer(*, ephemeral=False) | "I'm working on it" — buys you up to 15 minutes for a followup |
| followup(*, content="", embeds=None, components=None, ephemeral=False, allowed_mentions=None) | Reply after a defer (or send additional messages) |
| send_modal(*, title, custom_id, fields=None) | Open a modal — use as the FIRST response, not after a defer |
ctx.kv / ctx.sql / ctx.ephemeral / ctx.http / ctx.metrics
Full method signatures are in the dedicated sections above (KV, SQL, Ephemeral, HTTP, Metrics).
Plugin decorators
| Decorator | Handler signature |
|---|---|
| @plugin.on_install | (ctx) |
| @plugin.on_enable | (ctx) |
| @plugin.on_ready | (ctx) |
| @plugin.on_disable | (ctx) |
| @plugin.on_uninstall | (ctx) |
| @plugin.on_event(event_type) | (ctx, event) |
| @plugin.on_slash_command(name) | (ctx, event) |
| @plugin.on_component(custom_id) | (ctx, event) |
| @plugin.on_modal_submit(custom_id) | (ctx, event) |
| @plugin.schedule(seconds) | (ctx) |
| @plugin.cron(spec) | (ctx) — 5-field UTC cron string |
| @plugin.on_dashboard(method_name) | (ctx, params) → dict |
Event payload reference
Complete shape with default values produced by mmo_maid_sdk.testing.make_event:
make_event("message_create", content="hi")
# {message_id, channel_id, guild_id, author_id, author_username, author_bot,
# content, timestamp}
make_event("interaction_create", command_name="ban", custom_id="")
# {interaction_id, interaction_type, guild_id, channel_id, user_id,
# command_name, custom_id, modal_values}
make_event("reaction_add", emoji="🎉")
# {message_id, channel_id, user_id, emoji, guild_id}
Exceptions reference
See the full table under Error handling.
SDK changelog
v0.5.1
- Added
allowed_mentionsparameter onctx.interaction.respond()andctx.interaction.followup()— controls which mentions actually ping (e.g.{"parse": []}suppresses all pings).
v0.5.0
- Added
ctx.metrics,ctx.sql,ctx.ephemeralsub-APIs. - Added
mmo_maid_sdk.testingmodule withMockContextandmake_event. - Added lifecycle decorators:
on_install,on_enable,on_disable,on_uninstall. - Added
@plugin.cron(spec)for wall-clock UTC scheduling (5-field cron). - Added bulk Discord operations:
add_role_bulk,remove_role_bulk,kick_bulk,timeout_bulk. - Added typed exception classes (10) for actionable error handling.
- Security 7 critical + 1 high SDK / sandbox findings fixed (audit, April 2026).
SDK v0.5.1 · Last updated April 2026 · Back to Dev Portal