Discord Components v2 and Approval Prompts
Covers components v2 containers, interactive payloads, and exec approvals for Discord messages in OpenClaw. Useful for developers building rich Discord interactions and approval flows.
Read this when
- Sending buttons, selects, or other Discord components from an agent
- Approving exec requests from inside Discord
Rich Discord message surfaces: components v2 containers, interaction handling, and approval prompts.
Interactive components
Agent messages on Discord can carry components v2 containers, which OpenClaw supports. Send them through the message tool with a components payload. Results from interactions come back to the agent as ordinary inbound messages and obey the existing Discord replyToMode settings.
components extends the shared message tool in a Discord-specific way. Whenever Discord is configured, OpenClaw exposes it, even if a different channel is currently active. Reach for presentation when one rich message has to work across channels; OpenClaw adapts portable presentation actions for each target.
Blocks that are supported:
text,section,separator,actions,media-gallery,file- A single select menu, or up to 5 buttons, fits in an action row
- Emoji work on buttons, link buttons included
- Select types:
string,user,role,mentionable,channel
Components are single use unless configured otherwise. To let buttons, selects, and forms be used again until they expire, set components.reusable=true.
Component sends, edits, and native command replies wait until callback registration completes. When a single-use choice is consumed, removal of its sibling callbacks finishes before any other interaction can resolve them. Should persistence be unavailable, OpenClaw reports the failure and retains the in-memory fallback for the current process.
Restricting who may click a button is done by setting allowedUsers on that button (Discord user IDs, tags, or *). Users who do not match get an ephemeral denial.
By default, component callbacks expire after 30 minutes. Change the callback registry lifetime for the default account with channels.discord.agentComponents.ttlMs, or per account with channels.discord.accounts.<accountId>.agentComponents.ttlMs. The value is in milliseconds, must be a positive integer, and cannot exceed 86400000 (24 hours). Review/approval workflows that need buttons to stay usable benefit from longer TTLs, but a longer window also means an old Discord message can still trigger an action. Pick the shortest TTL that fits, and keep the default where stale callbacks would be surprising.
An interactive model picker opens from the /model and /models slash commands, offering provider, model, and compatible runtime dropdowns plus a Submit step. /models add is deprecated: instead of registering models from chat, it returns a deprecation message. Only the invoking user can use the picker reply, which is ephemeral. Discord caps select menus at 25 options, so when the picker should show dynamically discovered models only for selected providers such as openai or vllm, add provider/* entries to agents.defaults.modelPolicy.allow.
File attachments:
- A
fileblock has to point at an attachment reference (attachment://<filename>) - Supply the attachment through
media/path/filePath(single file); for multiple files usemedia-gallery - When the upload name should match the attachment reference, override it with
filename - Text-block order and repeated paragraphs are preserved in attachment captions
Modal forms:
- Add
components.modalwith up to 5 fields - Field types:
text,checkbox,radio,select,role-select,user-select - A trigger button is added by OpenClaw automatically
Example:
{
channel: "discord",
action: "send",
to: "channel:123456789012345678",
message: "Optional fallback text",
components: {
reusable: true,
text: "Choose a path",
blocks: [
{
type: "actions",
buttons: [
{
label: "Approve",
style: "success",
allowedUsers: ["123456789012345678"],
},
{ label: "Decline", style: "danger" },
],
},
{
type: "actions",
select: {
type: "string",
placeholder: "Pick an option",
options: [
{ label: "Option A", value: "a" },
{ label: "Option B", value: "b" },
],
},
},
],
modal: {
title: "Details",
triggerLabel: "Open form",
fields: [
{ type: "text", label: "Requester" },
{
type: "select",
label: "Priority",
options: [
{ label: "Low", value: "low" },
{ label: "High", value: "high" },
],
},
],
},
},
}
Components v2 UI
Exec approvals and cross-context markers on Discord rely on components v2. Discord message actions may also take components for custom UI (advanced; you must construct a component payload via the discord tool), and legacy embeds still work but are not recommended.
- How long sent Discord component callbacks stay registered is controlled by
channels.discord.agentComponents.ttlMs(default1800000, maximum86400000). Per account:channels.discord.accounts.<id>.agentComponents.ttlMs. embedsare ignored whenever components v2 are present.- Plain URL previews are suppressed by default. When a single outbound link should expand, set
suppressEmbeds: falseon a message action.
Approvals
Approvals in Discord
Button-based approval handling works in Discord DMs, and approval prompts can optionally be posted in the originating channel.
Config path:
channels.discord.execApprovals.enabledchannels.discord.execApprovals.approvers(optional; when feasible it falls back tocommands.ownerAllowFrom)channels.discord.execApprovals.target(dm|channel|both, default:dm)agentFilter,sessionFilter,cleanupAfterResolve
Native exec approvals on Discord need enabled: true or enabled: "auto", plus at least one approver that resolves, coming either from execApprovals.approvers or from commands.ownerAllowFrom. If enabled is left unset, or is set to false, native exec approval delivery is turned off. Approvers for exec are not inferred by Discord from channel allowFrom, from legacy dm.allowFrom, or from direct-message defaultTo.
For group commands that are sensitive and owner-only, such as /diagnostics and /export-trajectory, OpenClaw delivers approval prompts and final results in private. A Discord DM is attempted first when the owner who invoked the command has a Discord owner route. If not, the first available owner route from commands.ownerAllowFrom, for example Telegram, is used instead.
The approval prompt becomes visible in the channel whenever target equals channel or both. Buttons work only for resolved approvers; anyone else gets an ephemeral denial. Because approval prompts carry the command text, channel delivery should be enabled only in channels you trust. Should the session key not yield a channel ID, OpenClaw delivers by DM instead.
The approval buttons Discord shows are the same shared ones other chat channels use. What the native Discord adapter contributes is mostly approver DM routing and channel fanout. Where those buttons exist, they serve as the main approval UX, so OpenClaw ought to add a manual /approve command only when the tool result reports that chat approvals are unavailable, or that manual approval is the sole option. If Discord's native approval runtime is inactive, the local /approve <id> <decision> prompt stays visible in OpenClaw. If the runtime is active yet no target can receive a native card, OpenClaw posts a fallback notice in the same chat containing the exact /approve command taken from the pending approval.
Gateway auth and approval resolution adhere to the shared Gateway client contract (plugin: IDs resolve via plugin.approval.resolve; all other IDs via exec.approval.resolve). By default, approvals expire after 30 minutes.
See Exec approvals.