Discord
Status: ready for DMs and guild channels via the official Discord gateway.
Quick setup
- **Message Content Intent**
- **Server Members Intent** (required for role allowlists and role-based routing; recommended for name-to-ID allowlist matching)
{
channels: {
discord: {
enabled: true,
token: "YOUR_BOT_TOKEN",
},
},
}Env fallback for the default account:
DISCORD_BOT_TOKEN=...openclaw gatewayopenclaw pairing list discord
openclaw pairing approve discord <CODE>Pairing codes expire after 1 hour.
Runtime model
- Gateway owns the Discord connection.
- Reply routing is deterministic: Discord inbound replies back to Discord.
- By default (
session.dmScope=main), direct chats share the agent main session (agent:main:main). - Guild channels are isolated session keys (
agent:<agentId>:discord:channel:<channelId>). - Group DMs are ignored by default (
channels.discord.dm.groupEnabled=false). - Native slash commands run in isolated command sessions (
agent:<agentId>:discord:slash:<userId>), while still carryingCommandTargetSessionKeyto the routed conversation session.
Access control and routing
- `pairing` (default)
- `allowlist`
- `open` (requires `channels.discord.dm.allowFrom` to include `"*"`)
- `disabled`
If DM policy is not open, unknown users are blocked (or prompted for pairing in `pairing` mode).
DM target format for delivery:
- `user:<id>`
- `<@id>` mention
Bare numeric IDs are ambiguous and rejected unless an explicit user/channel target kind is provided.
- `open`
- `allowlist`
- `disabled`
Secure baseline when `channels.discord` exists is `allowlist`.
`allowlist` behavior:
- guild must match `channels.discord.guilds` (`id` preferred, slug accepted)
- optional sender allowlists: `users` (IDs or names) and `roles` (role IDs only); if either is configured, senders are allowed when they match `users` OR `roles`
- if a guild has `channels` configured, non-listed channels are denied
- if a guild has no `channels` block, all channels in that allowlisted guild are allowed
Example:
{
channels: {
discord: {
groupPolicy: "allowlist",
guilds: {
"123456789012345678": {
requireMention: true,
users: ["987654321098765432"],
roles: ["123456789012345678"],
channels: {
general: { allow: true },
help: { allow: true, requireMention: true },
},
},
},
},
},
}If you only set `DISCORD_BOT_TOKEN` and do not create a `channels.discord` block, runtime fallback is `groupPolicy="open"` (with a warning in logs).
Mention detection includes:
- explicit bot mention
- configured mention patterns (`agents.list[].groupChat.mentionPatterns`, fallback `messages.groupChat.mentionPatterns`)
- implicit reply-to-bot behavior in supported cases
`requireMention` is configured per guild/channel (`channels.discord.guilds...`).
Group DMs:
- default: ignored (`dm.groupEnabled=false`)
- optional allowlist via `dm.groupChannels` (channel IDs or slugs)
Role-based agent routing
Use bindings[].match.roles to route Discord guild members to different agents by role ID. Role-based bindings accept role IDs only and are evaluated after peer or parent-peer bindings and before guild-only bindings. If a binding also sets other match fields (for example peer + guildId + roles), all configured fields must match.
{
bindings: [
{
agentId: "opus",
match: {
channel: "discord",
guildId: "123456789012345678",
roles: ["111111111111111111"],
},
},
{
agentId: "sonnet",
match: {
channel: "discord",
guildId: "123456789012345678",
},
},
],
}Developer Portal setup
1. Discord Developer Portal -> **Applications** -> **New Application**
2. **Bot** -> **Add Bot**
3. Copy bot token
- Message Content Intent
- Server Members Intent (recommended)
Presence intent is optional and only required if you want to receive presence updates. Setting bot presence (`setPresence`) does not require enabling presence updates for members.
- scopes: `bot`, `applications.commands`
Typical baseline permissions:
- View Channels
- Send Messages
- Read Message History
- Embed Links
- Attach Files
- Add Reactions (optional)
Avoid `Administrator` unless explicitly needed.
- server ID
- channel ID
- user ID
Prefer numeric IDs in OpenClaw config for reliable audits and probes.
Native commands and command auth
commands.nativedefaults to"auto"and is enabled for Discord.- Per-channel override:
channels.discord.commands.native. commands.native=falseexplicitly clears previously registered Discord native commands.- Native command auth uses the same Discord allowlists/policies as normal message handling.
- Commands may still be visible in Discord UI for users who are not authorized; execution still enforces OpenClaw auth and returns "not authorized".
See Slash commands for command catalog and behavior.
Feature details
- `[[reply_to_current]]`
- `[[reply_to:<id>]]`
Controlled by `channels.discord.replyToMode`:
- `off` (default)
- `first`
- `all`
Message IDs are surfaced in context/history so agents can target specific messages.
- `channels.discord.historyLimit` default `20`
- fallback: `messages.groupChat.historyLimit`
- `0` disables
DM history controls:
- `channels.discord.dmHistoryLimit`
- `channels.discord.dms["<user_id>"].historyLimit`
Thread behavior:
- Discord threads are routed as channel sessions
- parent thread metadata can be used for parent-session linkage
- thread config inherits parent channel config unless a thread-specific entry exists
Channel topics are injected as **untrusted** context (not as system prompt).
- `off`
- `own` (default)
- `all`
- `allowlist` (uses `guilds.<id>.users`)
Reaction events are turned into system events and attached to the routed Discord session.
This affects `/config set|unset` flows (when command features are enabled).
Disable:
{
channels: {
discord: {
configWrites: false,
},
},
}{
channels: {
discord: {
proxy: "http://proxy.example:8080",
},
},
}Per-account override:
{
channels: {
discord: {
accounts: {
primary: {
proxy: "http://proxy.example:8080",
},
},
},
},
}{
channels: {
discord: {
pluralkit: {
enabled: true,
token: "pk_live_...", // optional; needed for private systems
},
},
},
}Notes:
- allowlists can use `pk:<memberId>`
- member display names are matched by name/slug
- lookups use original message ID and are time-window constrained
- if lookup fails, proxied messages are treated as bot messages and dropped unless `allowBots=true`
Status only example:
{
channels: {
discord: {
status: "idle",
},
},
}Activity example (custom status is the default activity type):
{
channels: {
discord: {
activity: "Focus time",
activityType: 4,
},
},
}Streaming example:
{
channels: {
discord: {
activity: "Live coding",
activityType: 1,
activityUrl: "https://twitch.tv/openclaw",
},
},
}Activity type map:
- 0: Playing
- 1: Streaming (requires `activityUrl`)
- 2: Listening
- 3: Watching
- 4: Custom (uses the activity text as the status state; emoji is optional)
- 5: Competing
Config path:
- `channels.discord.execApprovals.enabled`
- `channels.discord.execApprovals.approvers`
- `agentFilter`, `sessionFilter`, `cleanupAfterResolve`
If approvals fail with unknown approval IDs, verify approver list and feature enablement.
Related docs: [Exec approvals](/tools/exec-approvals)
Tools and action gates
Discord message actions include messaging, channel admin, moderation, presence, and metadata actions.
Core examples:
- messaging:
sendMessage,readMessages,editMessage,deleteMessage,threadReply - reactions:
react,reactions,emojiList - moderation:
timeout,kick,ban - presence:
setPresence
Action gates live under channels.discord.actions.*.
Default gate behavior:
| Action group | Default |
|---|---|
| reactions, messages, threads, pins, polls, search, memberInfo, roleInfo, channelInfo, channels, voiceStatus, events, stickers, emojiUploads, stickerUploads, permissions | enabled |
| roles | disabled |
| moderation | disabled |
| presence | disabled |
Voice messages
Discord voice messages show a waveform preview and require OGG/Opus audio plus metadata. OpenClaw generates the waveform automatically, but it needs ffmpeg and ffprobe available on the gateway host to inspect and convert audio files.
Requirements and constraints:
- Provide a local file path (URLs are rejected).
- Omit text content (Discord does not allow text + voice message in the same payload).
- Any audio format is accepted; OpenClaw converts to OGG/Opus when needed.
Example:
message(action="send", channel="discord", target="channel:123", path="/path/to/audio.mp3", asVoice=true)Troubleshooting
- enable Message Content Intent
- enable Server Members Intent when you depend on user/member resolution
- restart gateway after changing intents
- verify `groupPolicy`
- verify guild allowlist under `channels.discord.guilds`
- if guild `channels` map exists, only listed channels are allowed
- verify `requireMention` behavior and mention patterns
Useful checks:
openclaw doctor
openclaw channels status --probe
openclaw logs --follow- `groupPolicy="allowlist"` without matching guild/channel allowlist
- `requireMention` configured in the wrong place (must be under `channels.discord.guilds` or channel entry)
- sender blocked by guild/channel `users` allowlist
If you use slug keys, runtime matching can still work, but probe cannot fully verify permissions.
- DM disabled: `channels.discord.dm.enabled=false`
- DM policy disabled: `channels.discord.dm.policy="disabled"`
- awaiting pairing approval in `pairing` mode
If you set `channels.discord.allowBots=true`, use strict mention and allowlist rules to avoid loop behavior.
Configuration reference pointers
Primary reference:
High-signal Discord fields:
- startup/auth:
enabled,token,accounts.*,allowBots - policy:
groupPolicy,dm.*,guilds.*,guilds.*.channels.* - command:
commands.native,commands.useAccessGroups,configWrites - reply/history:
replyToMode,historyLimit,dmHistoryLimit,dms.*.historyLimit - delivery:
textChunkLimit,chunkMode,maxLinesPerMessage - media/retry:
mediaMaxMb,retry - actions:
actions.* - presence:
activity,status,activityType,activityUrl - features:
pluralkit,execApprovals,intents,agentComponents,heartbeat,responsePrefix
Safety and operations
- Treat bot tokens as secrets (
DISCORD_BOT_TOKENpreferred in supervised environments). - Grant least-privilege Discord permissions.
- If command deploy/state is stale, restart gateway and re-check with
openclaw channels status --probe.