Capability catalog
The 18 capabilities a plugin can request: tier, what each allows and refuses, and the ctx methods it unlocks.
capabilities_required. Server owners see this list at install time and can refuse any of them. A call that needs a refused capability raises CapabilityError and does nothing. yourbot validate detects the ones your code uses.
Safe
5 capabilitiesdiscord:add_reaction
Add reactionsReact to messages with emoji.
- Adding unicode and custom server emoji reactions
- Powering polls, status indicators, vote tally features
- Removing other users' reactions (would require a separate capability)
- Reacting in DMs
Low-impact, but still a write surface — customers may want to know if your plugin will be reacting on their server.
discord:edit_message
Edit messagesUpdate messages your plugin (or anyone the bot can edit) previously posted.
- Editing messages your plugin sent
- Editing other bot messages where Discord allows
- Updating embeds, components, content
- Editing messages from users (Discord doesn't allow this for bots)
- Editing messages older than ~14 days for bulk operations
Edits change the visible state of a server without a new notification. Declaring it lets customers anticipate that your plugin will modify in-place rather than only post-and-forget.
discord:send_message
Send messagesPost new messages into channels the bot can see.
- ctx.discord.send_message — plain text, embeds, components, files
- Posting in any channel where the bot has Send Messages permission (Discord still enforces)
- DM-style ephemeral replies via ctx.interaction.respond (interaction:respond)
- Sending DMs to arbitrary users (not yet supported)
- Sending in channels the bot was kicked from
- Bypassing Discord's per-channel rate limits or your plugin's outbound quota (60/min/server)
Messages are the most visible thing a plugin can do. Customers want to know upfront that your plugin will post in their server — surprise messages are a top reason plugins get uninstalled.
interaction:respond
Respond to interactionsReply to slash commands, button clicks, and modal submissions.
- ctx.interaction.respond — reply visible or ephemeral
- ctx.interaction.defer — buy time for slow operations
- ctx.interaction.send_modal — show a modal form
- Spontaneously sending messages — use discord:send_message for that
- Responding to interactions outside the 3-second initial window (use defer)
Interactions are how users invoke your plugin directly. If your manifest declares slash_commands, this capability is auto-added — Discord requires every slash command to respond.
storage:kv
Per-server key-value storageRead and write a private per-server key-value store (up to 10K keys, 64KB per value).
- Store per-server settings (welcome channel, role IDs, feature flags)
- Track counters and small leaderboards
- Cache fetched data with optional TTL
- Reading another plugin's KV store
- Cross-server data sharing — each (plugin, server) pair has its own namespace
- Storing files or large blobs (use a 3rd-party storage service via proxy:http instead)
Customers granting your plugin storage means it can keep data about their server forever. Declaring it lets them see, at install time, that your plugin will persist data — and lets them cleanly revoke it on uninstall.
Standard
7 capabilitiesdiscord:delete_message
Delete messagesRemove messages from a channel.
- Single message delete (ctx.discord.delete_message)
- Bulk delete for cleanup operations (subject to Discord's 2-100 per call + ≤14 days old)
- Restoring deleted messages (Discord provides no recovery API)
- Deleting messages older than 14 days via bulk (Discord limit)
Message deletion is irreversible. Auto-moderation plugins legitimately need this; declaring it makes customers consciously trust your plugin not to delete things it shouldn't.
discord:manage_webhooks
Manage webhooksCreate, execute and delete channel webhooks.
- Creating a webhook on a channel (ctx.discord.create_webhook)
- Posting through a webhook, including a custom name and avatar per message (ctx.discord.execute_webhook)
- Deleting webhooks the plugin manages (ctx.discord.delete_webhook)
- Executing or deleting webhooks the plugin did not create
- Impersonation beyond the display name and avatar Discord lets any webhook set
Webhooks post with an arbitrary display identity, which is useful for bridges and feeds but worth a conscious grant — customers should know a plugin can speak with faces other than its own.
discord:read
Read server dataFetch members, channels, roles, and recent messages from the server.
- ctx.discord.get_member / list_members / search_members
- ctx.discord.get_channel / list_roles
- ctx.discord.get_messages (recent messages in a channel)
- Reading direct messages between users
- Reading members across other servers your plugin isn't installed in
- Reading audit logs (separate Discord permission, not yet exposed)
Reading member lists and message history is privacy-sensitive — even read-only access lets your plugin build profiles. Declaring it makes the data access explicit at install time.
events:message_content
Read message contentReceive the full text of messages in your message_create / message_edit handlers.
- Reading event['content'] for message-related events
- Building keyword triggers, auto-mod, message analytics
- Accessing message content from DMs
- Retrieving message content older than what's in the gateway cache via events (use ctx.discord.get_messages with discord:read instead)
Message content is the most privacy-sensitive event field — most plugins genuinely don't need it (they react to commands, joins, reactions, etc.). Declaring it makes a clear privacy statement to customers, and Discord requires it as a separate intent.
Unlocks data in event payloads rather than a method.
proxy:http
Outbound HTTP requestsMake HTTP calls to specifically declared external domains through the platform proxy.
- ctx.http.get / post / request — only to domains in proxy_domains_requested
- Standard HTTP methods, JSON request/response, status codes
- Customer-configurable per-domain quotas (default 30/min/server)
- Direct socket access (sandbox blocks it)
- Calling domains not in your declared list
- Calls to localhost / private IP ranges (proxy refuses)
Network egress is the biggest sandbox risk — a compromised plugin could exfiltrate data. The proxy enforces your declared domain list strictly, so customers know exactly which third parties their server's data may reach.
storage:secrets
Encrypted plugin secretsRead and write encrypted secrets (API keys, tokens) via ctx.secrets — values are AES-GCM-encrypted at rest and never visible in plugin stdout, KV, or logs.
- ctx.secrets.get('OPENAI_API_KEY') — reads the dev's configured default, or a per-server override
- ctx.secrets.set('user_token', tok) — store per-server runtime values (refresh tokens, OAuth state)
- ctx.secrets.delete('user_token') — clear a per-server value
- Reading other plugins' secrets (each plugin has its own namespace)
- Setting the dev-level default (that's dashboard-only — plugins only override at the server scope)
- Listing keys you haven't fetched (no enumerate / list API for security — devs must know the key name)
Encrypted secrets let your plugin authenticate to third parties without leaking credentials through git, KV, or logs. Customers granting this know your plugin will store small encrypted values; the platform's master key is the only way to decrypt them.
storage:sql
Sandboxed SQL storageRead and write rows in a private per-plugin Postgres schema (auto-created, no DBA needed).
- Define your own tables, indexes, and queries via ctx.sql.execute / query
- Join across your own tables for analytics or leaderboards too big for KV
- Transactional semantics (each query auto-commits unless inside ctx.sql.transaction)
- Reading another plugin's schema or the platform's tables
- Direct connections — no DSN, no psql access, no creds in env
- Long-running queries (timeout enforced)
- Unparameterised SQL (must use %s placeholders — concatenation will be rejected at review)
SQL is powerful enough that misuse can leak customer data or degrade other plugins. Requires staff review on first version with this capability so we can sanity-check your queries are bounded.
Dangerous
6 capabilitiesdiscord:ban_members
Ban membersPermanently bar members from the server.
- ctx.discord.ban_member
- ctx.discord.unban_member
- Bans persist until manually lifted
- Banning across servers (each server has its own ban list)
- Banning users above your bot's role hierarchy
Bans are the strongest punishment a plugin can dispense — they're effectively permanent unless reversed. Customers grant this only to plugins they fully trust. Be conservative about when to invoke it.
discord:kick_members
Kick membersRemove members from the server (they can rejoin via invite).
- ctx.discord.kick_member (single)
- ctx.discord.kick_bulk (multiple)
- Permanently barring rejoin (use discord:ban_members for that)
- Kicking members above your bot's role hierarchy
Kicks affect real users. Customers expect plugins with this capability to use it judiciously — describe the exact trigger conditions in your plugin description.
discord:manage_channels
Manage channelsCreate, edit, or delete channels in the server.
- Create channels (text/voice/forum/category)
- Edit channel name, topic, position, permissions
- Delete channels
- Modifying server-wide settings (name, icon, region)
- Creating channels above your bot's role hierarchy
Channel management can permanently destroy data (deleted channels can't be restored). Customers will see this in the consent screen and likely want a clear explanation in your plugin description — be specific about which channels your plugin will touch.
discord:manage_roles
Manage rolesAdd or remove roles from members.
- ctx.discord.add_role / remove_role (single member)
- ctx.discord.add_role_bulk / remove_role_bulk
- Creating role-reward plugins, level-up systems, reaction roles
- Creating or deleting roles (separate capability not yet exposed)
- Assigning roles above your bot's role hierarchy
Roles control what users can see and do. A buggy plugin with this capability can lock customers out of their own server — declare it so customers know to test in a staging server first.
discord:moderate_members
Timeout membersPut members into Discord 'timeout' (temporary message/voice mute).
- ctx.discord.timeout_member (single)
- ctx.discord.timeout_bulk (multiple)
- Setting a duration up to 28 days per Discord's limit
- Permanently muting (use kick/ban instead)
- Timing out members above your bot's role hierarchy
Timeouts affect real people's experience in the server. Required separately from ban/kick because some auto-mod plugins want timeout-only with no escalation.
proxy:websocket
Live WebSocket connectionsOpen and maintain a persistent two-way connection to a declared external host, and optionally authenticate it with a stored secret.
- ctx.ws.ensure / send / close — to exact hosts in proxy_domains_requested, OR to a host a server admin approves at setup (ctx.ws.allow_host)
- A long-lived bidirectional stream (data flows continuously, both directions)
- Authenticating the connection with a secret you stored (the platform injects it; the plugin never sees the value)
- Direct socket access (the platform's broker holds the socket, not the sandbox)
- Connecting to hosts neither declared nor admin-approved (exact host, not a wildcard)
- Connections to localhost / private IP ranges (refused, re-checked after connect)
- Approving a host without a server admin (Manage Server) action
- Reading or relaying a secret bound to a different domain
A persistent, bidirectional, authenticated channel is a much larger trust grant than a one-shot HTTP call — it is the natural target for a compromised plugin. The platform holds the socket and enforces SSRF checks for the whole life of the connection. Destinations are either declared up front (staff-reviewed) or approved at setup by a server admin for their own server (e.g. a game-server IP) — the customer always controls where their data goes. Secrets are only injected to the precise domain they are bound to. Reviewed before publish.