Mainstream messaging
Discord access control
Who may reach the bot, which guild channels it answers in, and which Discord actions it is allowed to take.
Access control and routing
DM policy
channels.discord.dmPolicy controls DM access. channels.discord.allowFrom is the canonical DM allowlist.
pairing(default)allowlist(requires at least oneallowFromsender)open(requireschannels.discord.allowFromto include"*")disabled
If DM policy is not open, unknown users are blocked (or prompted for pairing in pairing mode).
Multi-account precedence:
- Omitted account
dmPolicyandgroupPolicyinherit the channel root. Explicit account policies win; with neither scope set, defaults remainpairingandallowlistrespectively. channels.discord.accounts.default.allowFromapplies only to thedefaultaccount.- For one account,
allowFromtakes precedence over legacydm.allowFrom. - Named accounts inherit
channels.discord.allowFromwhen their ownallowFromand legacydm.allowFromare unset. - Named accounts do not inherit
channels.discord.accounts.default.allowFrom.
Legacy channels.discord.dm.policy and channels.discord.dm.allowFrom are still read for compatibility. openclaw doctor --fix migrates them to dmPolicy and allowFrom when it can do so without changing access.
DM target format for delivery:
user:<id><@id>mention
Bare numeric IDs normally resolve as channel IDs when a channel default is active, but IDs listed in the account's effective DM allowFrom are treated as user DM targets for compatibility.
Access groups
Discord DMs and text command authorization can use dynamic accessGroup:<name> entries in channels.discord.allowFrom.
Access group names are shared across message channels. Use type: "message.senders" for a static group whose members are expressed in each channel's normal allowFrom syntax, or type: "discord.channelAudience" when a Discord channel's current ViewChannel audience should define membership dynamically. Shared access-group behavior: Access groups.
{accessGroups: {operators: { type: "message.senders", members: { "*": ["global-owner-id"], discord: ["discord:123456789012345678"], telegram: ["987654321"], },},},channels: {discord: { dmPolicy: "allowlist", allowFrom: ["accessGroup:operators"],},},}A Discord text channel has no separate member list. type: "discord.channelAudience" models membership as: the DM sender is a member of the configured guild and currently has effective ViewChannel permission on the configured channel after role and channel overwrites are applied.
Example: allow anyone who can see #maintainers to DM the bot, while keeping DMs closed to everyone else.
{accessGroups: {maintainers: { type: "discord.channelAudience", guildId: "1456350064065904867", channelId: "1456744319972282449", membership: "canViewChannel",},},channels: {discord: { dmPolicy: "allowlist", allowFrom: ["accessGroup:maintainers"],},},}You can mix dynamic and static entries:
{accessGroups: {maintainers: { type: "discord.channelAudience", guildId: "1456350064065904867", channelId: "1456744319972282449",},},channels: {discord: { dmPolicy: "allowlist", allowFrom: ["accessGroup:maintainers", "discord:123456789012345678"],},},}Lookups fail closed. If Discord returns Missing Access, the member lookup fails, or the channel belongs to a different guild, the DM sender is treated as unauthorized.
Enable the Discord Developer Portal Server Members Intent when using channel-audience access groups. DMs do not include guild member state, so OpenClaw resolves the member through Discord REST at authorization time.
Guild policy
Guild handling is controlled by channels.discord.groupPolicy:
openallowlistdisabled
Secure baseline when channels.discord exists is allowlist.
allowlist behavior:
- guild must match
channels.discord.guilds(idpreferred, slug accepted) - optional sender allowlists:
users(stable IDs recommended) androles(role IDs only); if either is configured, senders are allowed when they matchusersORroles - direct name/tag matching is disabled by default; enable
channels.discord.dangerouslyAllowNameMatching: trueonly as break-glass compatibility mode - names/tags are supported for
users, but IDs are safer;openclaw security auditwarns when name/tag entries are used - if a guild has
channelsconfigured, non-listed channels are denied - if a guild has no
channelsblock, all channels in that allowlisted guild are allowed
Example:
{channels: {discord: { groupPolicy: "allowlist", guilds: { "123456789012345678": { requireMention: true, ignoreOtherMentions: true, users: ["987654321098765432"], roles: ["123456789012345678"], channels: { general: { enabled: true }, help: { enabled: true, requireMention: true }, }, }, },},},}The legacy per-channel allow key is migrated to enabled by openclaw doctor --fix.
Without a channels.discord block, the Gateway does not auto-start Discord from DISCORD_BOT_TOKEN. Once the block exists, DISCORD_BOT_TOKEN remains the default-account token fallback. Passing --ambient-channels opts into env-only auto-configuration; that path uses groupPolicy="allowlist" and logs a warning, even if channels.defaults.groupPolicy is open.
Mentions and group DMs
Guild messages are mention-gated by default.
Mention detection includes:
- explicit bot mention
- configured mention patterns (
agents.entries.*.groupChat.mentionPatterns, fallbackmessages.groupChat.mentionPatterns) - implicit reply-to-bot behavior in supported cases
When writing outbound Discord messages, use canonical mention syntax: <@USER_ID> for users, <#CHANNEL_ID> for channels, and <@&ROLE_ID> for roles. Do not use the legacy <@!USER_ID> nickname mention form.
requireMention is configured per guild/channel (channels.discord.guilds...).
ignoreOtherMentions optionally drops messages addressed to another identity but not the bot. This covers explicit user/role mentions (excluding @everyone/@here) and replies to another non-webhook bot. An explicit mention of the current bot still wins.
Group DMs:
- default: ignored (
dm.groupEnabled=false) - optional allowlist via
dm.groupChannels(channel IDs or slugs)
Guild channel maps are allowlists
A guild entry with no channels map lets the bot work in every channel it can see, subject to the guild's requireMention and users rules. Adding even one channel entry turns the map into an allowlist: any channel not matched by an entry is denied, not merely left at guild defaults.
This surprises people who add one channel to give it special settings and find the bot has gone silent everywhere else. Use the "*" wildcard key to keep the rest of the guild reachable:
{ channels: { discord: { guilds: { YOUR_SERVER_ID: { requireMention: true, users: ["YOUR_USER_ID"], channels: { // always-on room: everyone in it can talk to the bot, no mention needed YOUR_CHANNEL_ID: { enabled: true, requireMention: false, users: ["*"] }, // every other channel keeps the guild defaults "*": { enabled: true, requireMention: true }, }, }, }, }, },}Channel entries override guild-level values, so a channel entry with users: ["*"] opens that one room to any sender even when the guild users list is narrow. Entries match by channel ID, name, or slug, and a thread falls back to its parent channel's entry.
Applying access-policy changes
For running Discord accounts, policy-only changes saved in the Control UI apply
through the Gateway's validated runtime config publication without restarting the
Discord connection or waiting for active Control UI turns to finish. This covers
groupPolicy, dmPolicy, allowFrom, dm, guilds, allowBots, and
dangerouslyAllowNameMatching, both at channels.discord and under
channels.discord.accounts.<accountId>.
New messages and interactions use the published policy, including guild/channel membership, user and role allowlists, and mention requirements. Name-based entries are resolved and cached for the policy revision before admission; already admitted work retains its existing context. If a name-policy lookup cannot finish within an interaction's response budget, components show an ephemeral policy-updating message and autocomplete returns no choices. A later interaction uses the resolved policy; the expired interaction is never resumed.
Token, application ID, proxy, intents, command registration, voice configuration, and account enablement still use the channel's restart path and drain deferral. A write that mixes policy and restart-required settings stays one deferred transaction. Manual channel stop/start reads the committed config; it does not publish a pending transport change from disk.
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", }, }, ],}Native commands and command auth
commands.nativedefaults to"auto"and is enabled for Discord.- Per-channel override:
channels.discord.commands.native. commands.native=falseskips Discord slash-command registration and cleanup during startup. Previously registered commands may remain visible in Discord until you remove them from the Discord app.- Native command auth uses the same Discord allowlists/policies as normal message handling.
- Commands may still be visible in the Discord UI for unauthorized users; execution enforces OpenClaw auth and replies "not authorized".
- Default slash command settings:
ephemeral: true(channels.discord.slashCommand.ephemeral).
See Slash commands for the command catalog and behavior.
Tools and action gates
Discord message actions cover messaging, channel admin, moderation, presence, and metadata.
Core examples:
- messaging:
sendMessage,readMessages,editMessage,deleteMessage,threadReply - reactions:
react,reactions,emoji-list - moderation:
timeout,kick,ban - presence:
setPresence
Use emoji-list to discover the current server's custom emoji:
{ "action": "emoji-list", "channel": "discord", "limit": 25 }guildId defaults to the current conversation's server; provide it explicitly to query another server. Results are sorted by name, and limit defaults to and cannot exceed 100:
{ "ok": true, "emojis": [ { "name": "dance", "identifier": "dance:456", "animated": true }, { "name": "party", "identifier": "party:123" } ]}Pass identifier directly to react. Discord accepts Unicode emoji, custom name:id identifiers, and the <:name:id> or <a:name:id> forms. emoji-list, react, and reactions are all controlled by channels.discord.actions.reactions.
The event-create action accepts an optional image parameter (URL or local file path) to set the scheduled event cover image.
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 |