Matrix Channel Plugin: Setup and Configuration
Learn how to install and configure the Matrix channel plugin for OpenClaw, covering setup, authentication, and auto-join. Essential for developers integrating Matrix messaging.
Read this when
- Setting up Matrix in OpenClaw
- Configuring Matrix E2EE and verification
Matrix is a downloadable channel plugin (@openclaw/matrix) that wraps the official matrix-js-sdk. It handles DMs, rooms, threads, media, reactions, polls, location data, and E2EE.
Install
openclaw plugins install @openclaw/matrix
When only bare plugin specs are given, ClawHub is checked first, with npm as the fallback. You can force a particular source through openclaw plugins install clawhub:@openclaw/matrix or npm:@openclaw/matrix. For a local checkout, use openclaw plugins install ./path/to/local/matrix-plugin.
The plugin gets registered and enabled via plugins install; a separate enable step isn't required. Until you configure it as described below, the channel remains inactive. General installation guidance lives in Plugins.
Setup
- Set up a Matrix account on your homeserver.
- Fill in
channels.matrixusing eitherhomeserverplusaccessToken, orhomeservercombined withuserIdandpassword. - Restart the gateway.
- Send a DM to the bot, or add it to a room. New invites are only accepted when
autoJoinpermits them.
Interactive setup
openclaw channels add
openclaw configure --section channels
The wizard collects the homeserver URL, an auth method (token or password), a user ID (only for password auth), an optional device name, an E2EE toggle, and room access or auto-join preferences. If MATRIX_* env vars are already present and no saved auth exists for the account, a shortcut using those env vars is offered. Resolve room names before you save an allowlist with openclaw channels resolve --channel matrix "Project Room". Turning on E2EE in the wizard performs the same bootstrap as openclaw matrix encryption setup.
Minimal config
Token-based:
{
channels: {
matrix: {
enabled: true,
homeserver: "https://matrix.example.org",
accessToken: "syt_xxx",
dm: { policy: "pairing" },
},
},
}
Password-based (the token gets cached after the first login):
{
channels: {
matrix: {
enabled: true,
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
password: "replace-me", // pragma: allowlist secret
deviceName: "OpenClaw Gateway",
},
},
}
SecretRefs for tokens and passwords follow the shared source-specific provider-alias rules, including for named accounts. An explicit matching env provider still applies its allowlist; an empty allowlist blocks all variables.
Auto-join
channels.matrix.autoJoin is set to "off" by default, meaning the bot won't show up in new rooms or DMs from fresh invites until you join manually. OpenClaw can't distinguish a DM from a group at invite time, so every invite is routed through autoJoin first; dm.policy only takes effect later, after the bot has joined and the room type is determined.
Warning
Use
autoJoin: "allowlist"together withautoJoinAllowlistto limit which invites are accepted, orautoJoin: "always"to accept all invites.
autoJoinAllowlistaccepts only a literal room ID (!roomId:server, or the suffixless!roomIdvariant from room version 12 and newer),#alias:server, or*. Plain room names are not accepted; aliases are resolved against the homeserver, not against state the invited room claims.
{
channels: {
matrix: {
autoJoin: "allowlist",
autoJoinAllowlist: ["!ops:example.org", "#support:example.org"],
groups: {
"!ops:example.org": { requireMention: true },
},
},
},
}
Group join introductions
When the bot joins an allowed group room, it posts a single introduction based on the room name, topic, and up to 100 readable recent room messages. If history can't be read, the introduction relies only on available metadata and doesn't fabricate room activity.
Introductions are on by default. To turn them off, set channels.matrix.joinIntro: false, or use channels.matrix.accounts.<accountId>.joinIntro to override for one account. Direct rooms never get introductions. Only an actual join transition triggers one: an unaccepted invite, a startup snapshot of an existing room, or a profile update while already joined won't. This leaves autoJoin unchanged, which defaults to "off".
For room admission, once-per-room behavior, and the no-tools turn that treats room content as untrusted, see group join introductions.
Allowlist target formats
Matrix user IDs are case-sensitive. Copy the exact @user:server value Matrix reports for every allowlist, approver, and approval-target field. If an existing config used different casing, update it manually; OpenClaw cannot safely infer or rewrite the intended account because case-distinct IDs can identify different users.
- Direct messages (
dm.allowFrom,groupAllowFrom,groups.<room>.users): rely on@user:server. By default, display names are ignored since they can change; only setdangerouslyAllowNameMatching: truewhen you explicitly need display-name compatibility. - Approval forwarding (
approvals.exec.targets[].tocombined withchannel: "matrix"): useuser:@user:server, matching Matrix casing exactly. - Room allowlist keys (
groups, plus the older aliasrooms): go with!room:server(or the suffixless!roomvariant on room version 12 and newer) or#alias:server. UnlessdangerouslyAllowNameMatching: trueis set, plain names are not considered. - Invite allowlists (
autoJoinAllowlist): choose!room:server(or suffixless!roomon room version 12+),#alias:server, or*. Plain names are rejected outright.
Account ID normalization
The wizard turns a friendly name into a normalized account ID (Ops Bot becomes ops-bot). To prevent account collisions, punctuation in scoped environment variable names is hex-escaped: - (0x2D) is transformed into _X2D_, so ops-prod corresponds to the env prefix MATRIX_OPS_X2D_PROD_.
Cached credentials
Account credentials for Matrix are cached in the shared state/openclaw.sqlite plugin state. When those cached credentials are present, OpenClaw considers Matrix configured even if the config file lacks an accessToken, covering setup, openclaw doctor, and channel-status checks. During upgrades, the deprecated ~/.openclaw/credentials/matrix/credentials*.json files are imported via openclaw doctor --fix, the SQLite rows are verified, and then the files are archived.
Environment variables
Environment variables backed by config keys, applied when the matching config key is not set. The default account uses names without prefixes; for named accounts, the account token is inserted before the suffix (see normalization).
| Default account | Named account (<ID> = account token) |
|---|---|
MATRIX_HOMESERVER | MATRIX_<ID>_HOMESERVER |
MATRIX_ACCESS_TOKEN | MATRIX_<ID>_ACCESS_TOKEN |
MATRIX_USER_ID | MATRIX_<ID>_USER_ID |
MATRIX_PASSWORD | MATRIX_<ID>_PASSWORD |
MATRIX_DEVICE_ID | MATRIX_<ID>_DEVICE_ID |
MATRIX_DEVICE_NAME | MATRIX_<ID>_DEVICE_NAME |
For account ops, the names become MATRIX_OPS_HOMESERVER, MATRIX_OPS_ACCESS_TOKEN, and so forth. MATRIX_HOMESERVER (including any *_HOMESERVER scoped variant) cannot be assigned from a workspace .env; refer to Workspace .env files.
Note
The recovery key is not a config-backed environment variable: OpenClaw never reads it from the environment on its own. CLI guidance text recommends piping it through a shell variable called
MATRIX_RECOVERY_KEYfor the default account, orMATRIX_RECOVERY_KEY_<ID>(plain uppercased account ID, without hex-escaping) for a named account, as described in Verify this device with a recovery key.
Configuration example
A practical starting point that includes DM pairing, a room allowlist, and E2EE:
{
channels: {
matrix: {
enabled: true,
homeserver: "https://matrix.example.org",
accessToken: "syt_xxx",
encryption: true,
dm: {
policy: "pairing",
sessionScope: "per-room",
threadReplies: "off",
},
groupPolicy: "allowlist",
groupAllowFrom: ["@admin:example.org"],
groups: {
"!roomid:example.org": { requireMention: true },
},
autoJoin: "allowlist",
autoJoinAllowlist: ["!roomid:example.org"],
threadReplies: "inbound",
replyToMode: "off",
streaming: { mode: "partial" },
},
},
}
Streaming previews
Matrix reply streaming requires explicit activation. streaming.mode determines how OpenClaw delivers the assistant reply while it is being generated; streaming.block.enabled decides whether each finished block remains a separate Matrix message.
{
channels: {
matrix: {
streaming: { mode: "partial" },
},
},
}
For live answer previews without visible tool or progress lines:
{
channels: {
matrix: {
streaming: {
mode: "partial",
preview: {
toolProgress: false,
},
},
},
},
}
The complete configuration accepts { mode, chunkMode, block, preview, progress }:
{
channels: {
matrix: {
streaming: {
mode: "progress",
progress: {
label: "auto", // pick from configured or built-in labels (false to hide)
labels: ["Thinking", "Writing", "Searching"], // candidates for label: "auto"
maxLines: 8, // max rolling progress lines (default: 8)
maxLineChars: 120, // max chars per line before truncation (default: 120)
toolProgress: true, // show tool/progress activity (default: true)
},
},
},
},
}
progress.label: a custom label,"auto"or unset to use a configured or built-in label, orfalseto suppress it.progress.labels: candidates apply only whenlabelequals"auto"or is unset.progress.maxLines: the maximum number of rolling progress lines held in the draft; anything older gets trimmed beyond this point.progress.maxLineChars: the character cap for each compact progress line before truncation kicks in.progress.toolProgress: when set totrue(the default), live tool and progress activity shows up in the draft.
streaming.mode | Behavior |
|---|---|
"off" (default) | Hold the complete reply, then send it in one go. |
"partial" | Modify a single normal text message in place as the model writes the current block. Stock clients may alert on the first preview rather than the final edit. |
"quiet" | Identical to "partial" except the message is a notice that does not notify. Recipients get notified only when a per-user push rule matches the finalized edit (details below). |
"progress" | Emits separate compact progress lines through a progress draft. |
streaming.block.enabled (default false) operates independently of streaming.mode:
streaming.mode | block.enabled: true | block.enabled: false (default) |
|---|---|---|
"partial" / "quiet" | Draft the current block live, keep finished blocks as messages | Draft the current block live, finalize it in place |
"off" | One notifying Matrix message per completed block | One notifying Matrix message for the whole reply |
Notes:
- If a preview exceeds Matrix's per-event size limit, OpenClaw halts preview streaming and switches to final-only delivery.
- Media replies always send attachments the normal way. When a visible preview cannot be safely reused, OpenClaw holds it until the full replacement is confirmed, then redacts it. If replacement delivery fails, arrives incomplete, or produces no visible event, the preview stays up.
- Tool-progress preview updates are enabled by default whenever preview streaming is active. Set
streaming.preview.toolProgress: falseto keep preview edits for answer text while routing tool progress through the standard delivery path. - Preview edits consume additional Matrix API calls. Leave
streaming.mode: "off"for the most conservative rate-limit behavior. - Legacy scalar or boolean
streamingvalues, along with the flatblockStreamingandchunkModekeys, get converted into this nested structure byopenclaw doctor --fix.
Voice messages
Inbound Matrix voice notes get transcribed before the room mention gate, so a voice note that says the bot name can trigger the agent in a requireMention: true room, and the agent receives the transcript instead of only an audio attachment placeholder.
Matrix relies on the shared audio media provider under tools.media.audio, such as OpenAI gpt-4o-mini-transcribe. See Media tools overview for provider setup and limits.
m.audioevents andm.fileevents carrying anaudio/*MIME type qualify.- In encrypted rooms, OpenClaw decrypts the attachment through the existing Matrix media path before transcription.
- The transcript is flagged as machine-generated and untrusted in the agent prompt.
- The attachment is marked as already transcribed so downstream media tools skip it.
- Set
tools.media.audio.enabled: falseto turn off audio transcription globally.
Approval metadata
Matrix native approval prompts are ordinary m.room.message events with OpenClaw-specific content under the com.openclaw.approval key. Stock clients still render the text body; OpenClaw-aware clients can read the structured approval id, kind, state, decisions, and exec or plugin details.
When a prompt is too long for a single Matrix event, OpenClaw splits the visible text into chunks and attaches com.openclaw.approval to the first chunk only. Allow and deny reactions bind to that first event, so long prompts keep the same approval target as single-event prompts.
Self-hosted push rules for quiet finalized previews
streaming.mode: "quiet" alerts recipients only after a block or turn is finalized; a per-user push rule must match the finalized preview marker. See Matrix push rules for quiet previews for the complete recipe.
Bot-to-bot rooms
By default, Matrix messages from other configured OpenClaw Matrix accounts are ignored. Use allowBots to deliberately allow inter-agent traffic:
{
channels: {
matrix: {
allowBots: "mentions", // true | "mentions"
groups: {
"!roomid:example.org": {
requireMention: true,
},
},
},
},
}
allowBots: truepulls in messages sent by other configured Matrix bot accounts, whether they appear in allowed rooms or in direct messages.allowBots: "mentions"only accepts those messages when the bot is explicitly mentioned in a room; DMs are exempt from this requirement.groups.<room>.allowBotslets you override the account-wide setting for a single room.- Messages from configured bots share the same bot loop protection. Set
channels.defaults.botLoopProtection, then adjust it per account withchannels.matrix.botLoopProtectionor per room withchannels.matrix.groups.<room>.botLoopProtection. - To prevent self-reply loops, OpenClaw still disregards messages originating from the same Matrix user ID.
- Since Matrix lacks a native bot flag, OpenClaw defines "bot-authored" as "sent by another configured Matrix account on this OpenClaw gateway".
When enabling bot-to-bot communication in shared rooms, enforce strict room allowlists and mention requirements.
Encryption and verification
In encrypted (E2EE) rooms, outbound image events rely on thumbnail_file so that image previews are encrypted with the full attachment; unencrypted rooms fall back to plain thumbnail_url. No setup is required, as the plugin automatically detects E2EE state.
All openclaw matrix commands support --verbose (full diagnostics), --json (machine-readable output), and --account <id> (multi-account setups). Output stays concise by default.
Enable encryption
openclaw matrix encryption setup
printf '%s\n' "$MATRIX_RECOVERY_KEY" | openclaw matrix encryption setup --recovery-key-stdin
This bootstraps secret storage and cross-signing, sets up a room-key backup if necessary, and then reports status and next steps. Useful flags:
--recovery-key-stdinpulls a recovery key from stdin without exposing it in process arguments;--recovery-key <key>remains for compatibility--force-reset-cross-signingdiscards the current cross-signing identity and generates a new one (for intentional use only)
For a new account, enable E2EE at creation time:
openclaw matrix account add \
--homeserver https://matrix.example.org \
--access-token syt_xxx \
--enable-e2ee
--encryption serves as an alias for --enable-e2ee. Manual config equivalent:
{
channels: {
matrix: {
enabled: true,
homeserver: "https://matrix.example.org",
accessToken: "syt_xxx",
encryption: true,
dm: { policy: "pairing" },
},
},
}
Status and trust signals
openclaw matrix verify status
openclaw matrix verify status --include-recovery-key --json
With --include-recovery-key, text output confirms when a raw recovery key is available and points you to add --json. The key itself never appears in text output; keep any JSON output containing a recovery key private.
verify status reports three independent trust signals (--verbose shows all of them):
Locally trusted: trusted only by this clientCross-signing verified: the SDK indicates verification via cross-signingSigned by owner: signed by your own self-signing key (diagnostic only)
Verified by owner is yes only when Cross-signing verified is yes; local trust or an owner signature alone is insufficient.
--allow-degraded-local-state returns best-effort diagnostics without first preparing the Matrix account; useful for offline or partially-configured probes.
Verify this device with a recovery key
Pipe the recovery key via stdin instead of passing it on the command line:
printf '%s\n' "$MATRIX_RECOVERY_KEY" | openclaw matrix verify device --recovery-key-stdin
The command reports three states:
Recovery key accepted: Matrix accepted the key for secret storage or device trust.Backup usable: room-key backup can be loaded with the trusted recovery material.Device verified by owner: this device has full Matrix cross-signing identity trust.
It exits non-zero when full identity trust is incomplete, even if the recovery key unlocked backup material. In that case, finish self-verification from another Matrix client:
openclaw matrix verify self
verify self waits for Cross-signing verified: yes before exiting successfully. Use --timeout-ms <ms> to tune the wait.
The literal-key form openclaw matrix verify device "<recovery-key>" also works, but the key ends up in shell history.
Bootstrap or repair cross-signing
openclaw matrix verify bootstrap
The repair/setup command for encrypted accounts. In order, it:
- bootstraps secret storage, reusing an existing recovery key when possible
- bootstraps cross-signing and uploads missing public keys
- marks and cross-signs the current device
- creates a server-side room-key backup if one does not already exist
If the homeserver requires UIA to upload cross-signing keys, OpenClaw tries no-auth first, then m.login.dummy, then m.login.password (requires channels.matrix.password).
Useful flags:
--recovery-key-stdin(pair withprintf '%s\n' "$MATRIX_RECOVERY_KEY" | ...) or--recovery-key <key>--force-reset-cross-signingto discard the current cross-signing identity (intentional only; requires the active recovery key stored or supplied with--recovery-key-stdin)
Room-key backup
openclaw matrix verify backup status
printf '%s\n' "$MATRIX_RECOVERY_KEY" | openclaw matrix verify backup restore --recovery-key-stdin
backup status indicates whether a server-side backup is present and if this device has the ability to decrypt it. To bring backed-up room keys into the local crypto store, use backup restore; drop --recovery-key-stdin when the recovery key is already saved to disk.
When you need to swap a damaged backup for a clean starting point (this accepts the loss of unrecoverable old history; it can also rebuild secret storage if the current backup secret cannot be loaded):
openclaw matrix verify backup reset --yes
Include --rotate-recovery-key only if the earlier recovery key should deliberately no longer unlock the new backup baseline.
Listing, requesting, and responding to verifications
openclaw matrix verify list
Shows the verification requests that are waiting for the selected account.
openclaw matrix verify request --own-user
openclaw matrix verify request --user-id @ops:example.org --device-id ABCDEF
Sends a verification request from this account. Self-verification is requested via --own-user (confirm the prompt in another Matrix client belonging to the same user); --user-id/--device-id/--room-id are for targeting a different person. The other targeting flags cannot be used together with --own-user.
For lower-level lifecycle operations, typically used while shadowing inbound requests from another client, these commands act on a specific request <id> (which is shown by verify list and verify request):
| Command | Purpose |
|---|---|
openclaw matrix verify accept <id> | Accept an inbound request |
openclaw matrix verify start <id> | Start the SAS flow |
openclaw matrix verify sas <id> | Print the SAS emoji or decimals |
openclaw matrix verify confirm-sas <id> | Confirm that the SAS matches what the other client shows |
openclaw matrix verify mismatch-sas <id> | Reject the SAS when the emoji or decimals do not match |
openclaw matrix verify cancel <id> | Cancel; takes optional --reason <text> and --code <matrix-code> |
When verification is tied to a specific direct-message room, accept, start, sas, confirm-sas, mismatch-sas, and cancel all accept --user-id and --room-id as DM follow-up hints.
Multi-account notes
Matrix CLI commands rely on the implicit default account unless --account <id> is provided. If multiple named accounts exist and channels.matrix.defaultAccount is missing, commands refuse to guess and ask for a choice. When E2EE is turned off or unavailable for a named account, errors point to that account's config key, for example channels.matrix.accounts.assistant.encryption.
Startup behavior
With encryption: true, startupVerification falls back to "if-unverified". At startup, a device that is not verified requests self-verification in another Matrix client, skipping duplicates and applying a cooldown (24 hours by default). Adjust this with startupVerificationCooldownHours or turn it off with startupVerification: "off".
A conservative crypto bootstrap pass also runs at startup, reusing the existing secret storage and cross-signing identity. If bootstrap state is corrupted, OpenClaw attempts a guarded repair even without channels.matrix.password; when the homeserver demands password UIA, startup logs a warning and remains non-fatal. Devices already signed by the owner are kept intact.
The complete upgrade flow is described in Matrix migration.
Verification notices
Lifecycle notices for Matrix verification are posted into the strict DM verification room as m.notice messages: request, ready (with "Verify by emoji" guidance), start/completion, and SAS (emoji/decimal) details when they are available.
Incoming requests from another Matrix client are tracked and auto-accepted. For self-verification, OpenClaw automatically starts the SAS flow and confirms its own side once emoji verification is available; you still need to compare and confirm "They match" in your Matrix client.
Verification system notices are not sent to the agent chat pipeline.
Deleted or invalid Matrix device
If verify status reports that the current device is no longer on the homeserver, set up a new OpenClaw Matrix device. For password login:
openclaw matrix account add \
--account assistant \
--homeserver https://matrix.example.org \
--user-id '@assistant:example.org' \
--password '<password>' \
--device-name OpenClaw-Gateway
For token auth, generate a fresh access token in your Matrix client or admin UI, then update OpenClaw:
openclaw matrix account add \
--account assistant \
--homeserver https://matrix.example.org \
--access-token '<token>'
Swap assistant for the account ID from the failed command, or leave out --account for the default account.
Device hygiene
Old OpenClaw-managed devices can pile up. List and remove them:
openclaw matrix devices list
openclaw matrix devices prune-stale
Crypto store
Matrix E2EE relies on the official matrix-js-sdk Rust crypto path, with fake-indexeddb serving as the IndexedDB shim. Crypto state is saved to crypto-idb-snapshot.json (with restrictive file permissions).
Encrypted runtime state is stored under ~/.openclaw/matrix/accounts/<account>/<homeserver>__<user>/<token-hash>/ and covers the sync store, crypto store, recovery key, IDB snapshot, thread bindings, and startup verification state. When the token changes but the account identity remains the same, OpenClaw reuses the best existing root so prior state stays visible.
A single older token-hash root can serve as a normal token-rotation continuity path. When OpenClaw logs matrix: multiple populated token-hash storage roots detected, check the account directory and move stale sibling roots into an _archive/ directory only after verifying the selected active root is healthy, rather than deleting them right away.
Profile management
openclaw matrix profile set --name "OpenClaw Assistant"
openclaw matrix profile set --avatar-url https://cdn.example.org/avatar.png
Pass both options in a single call. Matrix accepts mxc:// avatar URLs directly; using http:///https:// uploads the file first, then stores the resolved mxc:// URL into channels.matrix.avatarUrl (or the per-account override).
Threads
Matrix supports native threads for both automatic replies and message-tool sends. Two independent knobs control behavior:
Session routing (sessionScope)
dm.sessionScope determines how Matrix DM rooms map to OpenClaw sessions:
"per-user"(default): all DM rooms with the same routed peer share one session."per-room": each Matrix DM room gets its own session key, even for the same peer.
Explicit conversation bindings always win over sessionScope; bound rooms and threads keep their chosen target session.
Reply threading (threadReplies)
threadReplies decides where the bot posts its reply:
"off": replies are top-level. Inbound threaded messages stay on the parent session."inbound": reply inside a thread only when the inbound message was already in that thread."always": reply inside a thread rooted at the triggering message; that conversation routes through a matching thread-scoped session from the first trigger onward.
dm.threadReplies overrides this for DMs only - for example, keep room threads isolated while keeping DMs flat.
Thread inheritance and slash commands
- Inbound threaded messages include the thread root message as extra agent context.
- Message-tool sends auto-inherit the current Matrix thread when targeting the same room (or the same DM user target), unless an explicit
threadIdis provided. - DM user-target reuse only kicks in when current session metadata proves the same DM peer on the same Matrix account; otherwise OpenClaw falls back to normal user-scoped routing.
/focus,/unfocus,/agents,/session idle,/session max-age, and thread-bound/acp spawnall work in Matrix rooms and DMs.- Top-level
/focuscreates a new Matrix thread and binds it to the target session whenthreadBindings.spawnSessionsis enabled. - Running
/focusor/acp spawn --thread hereinside an existing Matrix thread binds that thread in place.
When OpenClaw detects a Matrix DM room colliding with another DM room on the same shared session, it posts a one-time m.notice pointing to the /focus escape hatch and suggesting a dm.sessionScope change. The notice only appears when thread bindings are enabled.
ACP conversation bindings
Matrix rooms, DMs, and existing Matrix threads can become durable ACP workspaces without changing the chat surface.
Fast operator flow:
- Run
/acp spawn codex --bind hereinside the Matrix DM, room, or existing thread to keep using. - In a top-level DM or room, the current DM/room stays the chat surface and future messages route to the spawned ACP session.
- Inside an existing thread,
--bind herebinds that current thread in place. /newand/resetreset the same bound ACP session in place./acp closecloses the ACP session and removes the binding.
--bind here does not create a child Matrix thread. threadBindings.spawnSessions gates /acp spawn --thread auto|here, where OpenClaw needs to create or bind a child thread.
Thread binding config
Matrix inherits global defaults from session.threadBindings and supports per-channel overrides:
threadBindings.enabledthreadBindings.idleHoursthreadBindings.maxAgeHoursthreadBindings.spawnSessions: gates both subagent and ACP thread spawns.- Deprecated
threadBindings.spawnSubagentSessions/threadBindings.spawnAcpSessionskeys are migrated tospawnSessionsbyopenclaw doctor --fix. threadBindings.defaultSpawnContext
Matrix thread-bound session spawns default on. Set threadBindings.spawnSessions: false to block top-level /focus and /acp spawn --thread auto|here from creating/binding Matrix threads. Set threadBindings.defaultSpawnContext: "isolated" when native subagent thread spawns should not fork the parent transcript.
Reactions
Reactions sent outbound, received as notifications, and acknowledged are all supported by Matrix.
Access to the outbound reaction tooling depends on channels.matrix.actions.reactions:
- A reaction can be attached to a Matrix event with
react. reactionsretrieves the reaction summary currently associated with a Matrix event.- Custom emoji are discovered via
emoji-listfrom the room packs in the active conversation and from your personal pack. - The bot's own reactions on that event are cleared by
emoji="". - Only the designated emoji reaction is removed from the bot using
remove: true.
emoji-list reads MSC2545 im.ponies.room_emotes packs from the authorized current room and im.ponies.user_emotes account data. It returns up to 100 sorted entries like { "name": "party", "identifier": "party", "url": "mxc://example.org/party" }; sticker-only entries are filtered out. Pass identifier to react: it is the plain shortcode stored directly as the Matrix reaction's m.relates_to.key, not the mxc:// media URL. Rendering custom reactions depends on the Matrix client, so url is included separately for clients or agents that need the image.
Resolution order (first defined value wins):
| Setting | Order |
|---|---|
ackReaction | per-account -> channel -> messages.ackReaction -> agent identity emoji fallback |
ackReactionScope | per-account -> channel -> messages.ackReactionScope -> default "group-mentions" |
reactionNotifications | per-account -> channel -> default "own" |
reactionNotifications: "own" forwards added m.reaction events when they target bot-authored Matrix messages; "off" disables reaction system events. Reaction removals are not synthesized into system events - Matrix surfaces those as redactions, not as standalone m.reaction removals.
History context
channels.matrix.historyLimitsets how many recent room messages are included asInboundHistorywhen a room message triggers the agent. Falls back tomessages.groupChat.historyLimit; effective default0if both are unset (disabled).- Matrix room history is room-only; DMs keep using normal session history.
- Room history is pending-only: OpenClaw buffers room messages that did not trigger a reply yet, then snapshots that window when a mention or other trigger arrives.
- The current trigger message is not included in
InboundHistory; it stays in the main inbound body for that turn. - Retries of the same Matrix event reuse the original history snapshot instead of drifting forward to newer room messages.
Context visibility
Matrix supports the shared contextVisibility control for supplemental room context such as fetched reply text, thread roots, and pending history.
contextVisibility: "all"is the default. Supplemental context is kept as received.contextVisibility: "allowlist"filters supplemental context to senders allowed by the active room/user allowlist checks.contextVisibility: "allowlist_quote"behaves likeallowlist, but still keeps one explicit quoted reply.
This affects supplemental context visibility only, not whether the inbound message itself can trigger a reply. Trigger authorization still comes from groupPolicy, groups, groupAllowFrom, and DM policy settings.
DM and room policy
{
channels: {
matrix: {
dm: {
policy: "allowlist",
allowFrom: ["@admin:example.org"],
threadReplies: "off",
},
groupPolicy: "allowlist",
groupAllowFrom: ["@admin:example.org"],
groups: {
"!roomid:example.org": { requireMention: true },
},
},
},
}
To silence DMs entirely while keeping rooms working, set dm.enabled: false:
{
channels: {
matrix: {
dm: { enabled: false },
groupPolicy: "allowlist",
groupAllowFrom: ["@admin:example.org"],
},
},
}
See Groups for mention-gating and allowlist behavior.
Pairing example for Matrix DMs:
openclaw pairing list matrix
openclaw pairing approve matrix <CODE>
If an unapproved Matrix user keeps messaging before approval, OpenClaw reuses the same pending pairing code and may send a reminder reply after a short cooldown instead of minting a new code.
See Pairing for the shared DM pairing flow and storage layout.
Direct room repair
If direct-message state drifts, OpenClaw can end up with stale m.direct mappings pointing at old solo rooms instead of the live DM. Inspect the current mapping for a peer:
openclaw matrix direct inspect --user-id @alice:example.org
Repair it:
openclaw matrix direct repair --user-id @alice:example.org
Both commands accept --account <id> for multi-account setups. The repair flow:
- prefers a strict 1:1 DM already mapped in
m.direct - falls back to any currently joined strict 1:1 DM with that user
- creates a fresh direct room and rewrites
m.directif no healthy DM exists
It does not delete old rooms automatically. It picks the healthy DM and updates the mapping so future Matrix sends, verification notices, and other direct-message flows target the right room.
Exec approvals
Matrix can act as a native approval client. Configure under channels.matrix.execApprovals (or channels.matrix.accounts.<account>.execApprovals for a per-account override):
enabled: route approvals through Matrix-native prompts. When unset, or set to"auto", this activates automatically once at least one approver can be identified; usefalseto turn it off explicitly.approvers: Matrix user IDs (@owner:example.org) permitted to approve exec requests. If not set, it defaults tochannels.matrix.dm.allowFrom.target: determines where prompts are delivered."dm"(the default) routes to approver DMs;"channel"routes to the source room or DM;"both"routes to both.agentFilter/sessionFilter: optional allowlists controlling which agents/sessions trigger Matrix delivery.
Authorization varies by approval type:
- Exec approvals rely on
execApprovals.approvers, withdm.allowFromas the fallback. - Plugin approvals are authorized exclusively through
dm.allowFrom.
Both types share Matrix reaction shortcuts and message updates. Approvers see reaction shortcuts on the primary approval message:
- ✅ allow once
- ❌ deny
- ♾️ allow always (when the effective exec policy permits it)
Fallback slash commands: /approve <id> allow-once, /approve <id> allow-always, /approve <id> deny.
Only resolved approvers can approve or deny. Exec approval channel delivery includes the command text; only enable channel or both in trusted rooms.
Related: Exec approvals.
Slash commands
Slash commands (/new, /reset, /model, /focus, /unfocus, /agents, /session, /acp, /approve, etc.) function directly in DMs. In rooms, OpenClaw also picks up commands prefixed with the bot's own Matrix mention, so @bot:server /new triggers the command path without a custom mention regex, keeping the bot responsive to the room-style @mention /command posts that Element and similar clients emit when a user tab-completes the bot before typing the command.
Authorization rules still apply: command senders must meet the same DM or room allowlist/owner policies as regular messages.
Multi-account
{
channels: {
matrix: {
enabled: true,
defaultAccount: "assistant",
dm: { policy: "pairing" },
accounts: {
assistant: {
homeserver: "https://matrix.example.org",
accessToken: "syt_assistant_xxx",
encryption: true,
},
alerts: {
homeserver: "https://matrix.example.org",
accessToken: "syt_alerts_xxx",
dm: {
policy: "allowlist",
allowFrom: ["@ops:example.org"],
threadReplies: "off",
},
},
},
},
},
}
Inheritance:
- Top-level
channels.matrixvalues serve as defaults for named accounts unless an account overrides them. - Scope an inherited room entry to a specific account with
groups.<room>.account. Entries withoutaccountare shared across accounts;account: "default"still works when the default account is configured at the top level.
Default account selection:
- Set
defaultAccountto choose the named account that implicit routing, probing, and CLI commands prefer. - If you have multiple accounts and one is literally named
default, OpenClaw uses it implicitly even whendefaultAccountis unset. - With multiple named accounts and no default selected, CLI commands refuse to guess; set
defaultAccountor pass--account <id>. - The top-level
channels.matrix.*block is only treated as the implicitdefaultaccount when its auth is complete (homeserver+accessToken, orhomeserver+userId+password). Named accounts remain discoverable fromhomeserver+userIdonce cached credentials cover auth.
Promotion:
- When OpenClaw promotes a single-account config to multi-account during repair or setup, it preserves the existing named account if one exists or
defaultAccountalready points at one. Only Matrix auth/bootstrap keys move into the promoted account; shared delivery-policy keys stay at the top level.
See Configuration reference for the shared multi-account pattern.
Private/LAN homeservers
By default, OpenClaw blocks private/internal Matrix homeservers for SSRF protection unless you opt in per account.
If your homeserver runs on localhost, a LAN/Tailscale IP, or an internal hostname, enable network.dangerouslyAllowPrivateNetwork for that account:
{
channels: {
matrix: {
homeserver: "http://matrix-synapse:8008",
network: {
dangerouslyAllowPrivateNetwork: true,
},
accessToken: "syt_internal_xxx",
},
},
}
CLI setup example:
openclaw matrix account add \
--account ops \
--homeserver http://matrix-synapse:8008 \
--allow-private-network \
--access-token syt_ops_xxx
This opt-in restricts access to trusted private or internal targets only. Public cleartext homeservers like http://matrix.example.org:8008 stay blocked. Whenever possible, prefer https://.
Proxying Matrix traffic
For a Matrix deployment that requires an explicit outbound HTTP(S) proxy, configure channels.matrix.proxy:
{
channels: {
matrix: {
homeserver: "https://matrix.example.org",
accessToken: "syt_bot_xxx",
proxy: "http://127.0.0.1:7890",
},
},
}
Named accounts can replace the top-level default with channels.matrix.accounts.<id>.proxy. Both runtime Matrix traffic and account status probes from OpenClaw use this same proxy setting.
Target resolution
Wherever OpenClaw requests a room or user target, Matrix accepts these forms:
- Users:
@user:server,user:@user:server, ormatrix:user:@user:server - Rooms:
!room:server,room:!room:server, ormatrix:room:!room:server(room IDs from version 12+ have no:serversuffix:!room,room:!room,matrix:room:!room; they are handled identically) - Aliases:
#alias:server,channel:#alias:server, ormatrix:channel:#alias:server
Matrix room IDs distinguish case. When setting explicit delivery targets, cron jobs, bindings, or allowlists, match the exact casing of the room ID from Matrix. Internal session keys are kept canonical for storage by OpenClaw, so those lowercase keys should not be used to derive Matrix delivery IDs.
Live directory resolution relies on the currently logged-in Matrix account:
- User lookups search the user directory of that homeserver.
- Room lookups take explicit room IDs and aliases directly. Name-based lookup for joined rooms is best-effort, and applies only to runtime room allowlists when
dangerouslyAllowNameMatching: trueis set. - A room name that cannot be mapped to an ID or alias is skipped during runtime allowlist resolution.
Configuration reference
Allowlist-style user fields (groupAllowFrom, dm.allowFrom, groups.<room>.users) accept full Matrix user IDs, which is the safest option. Non-ID entries are skipped by default. When dangerouslyAllowNameMatching: true is set, exact display-name matches from the Matrix directory are resolved at startup and again whenever the allowlist changes while the monitor runs; entries that cannot be resolved are ignored at runtime.
Room allowlist keys (groups, legacy rooms) should hold room IDs or aliases. Plain room-name keys are skipped by default; dangerouslyAllowNameMatching: true re-enables best-effort matching against joined room names.
Account and connection
enabled: turns the channel on or off.name: optional label shown for the account.defaultAccount: preferred account ID when multiple Matrix accounts exist.accounts: named per-account overrides. Top-levelchannels.matrixvalues serve as defaults.homeserver: homeserver URL, such ashttps://matrix.example.org.network.dangerouslyAllowPrivateNetwork: permits this account to reachlocalhost, LAN/Tailscale IPs, or internal hostnames.proxy: optional HTTP(S) proxy URL for Matrix traffic. Can be overridden per account.userId: full Matrix user ID (@bot:example.org).accessToken: access token for token-based auth. Plaintext and SecretRef values work across env/file/exec/store providers (Secrets Management).password: password for password-based login. Plaintext and SecretRef values supported.deviceId: explicit Matrix device ID.deviceName: device display name assigned at password-login time.avatarUrl: stored self-avatar URL for profile sync andprofile setupdates.initialSyncLimit: maximum events fetched during startup sync.
Encryption
encryption: enables E2EE. Default:false.startupVerification:"if-unverified"(default when E2EE is active) or"off". If this device is unverified, self-verification is requested automatically at startup.startupVerificationCooldownHours: cooldown before the next automatic startup request. Default:24.
Access and policy
groupPolicy: accepts"open","allowlist", or"disabled". Falls back to"allowlist"if not set.groupAllowFrom: restricts room traffic to a defined set of user IDs.mentionPatterns: defines room mention patterns using scoped regexes. An object holding{ mode: "allow"|"deny", allowIn: [roomId, ...], denyIn: [roomId, ...] }determines whether the configuredagents.entries.*.groupChat.mentionPatternsapply on a per-room basis.dm.enabled: when set tofalse, all direct messages are skipped. The fallback istrue.dm.policy: options are"pairing"(the default),"allowlist","open", or"disabled". This takes effect only after the bot has joined and identified the room as a DM; invite processing remains unaffected.dm.allowFrom: limits DM traffic to a specified set of user IDs.dm.sessionScope: either"per-user"(default) or"per-room".dm.threadReplies: overrides reply threading for DMs only, using"off","inbound", or"always".allowBots: permits incoming messages from other configured Matrix bot accounts, viatrueor"mentions".allowlistOnly: whentrueis active, all current DM policies, excluding"disabled", plus the"open"group policies are forced to"allowlist". The"disabled"policies stay unchanged.dangerouslyAllowNameMatching: whentrueis set, directory lookup by display name works for user allowlist entries, and room-name lookup applies to room allowlist keys. Full@user:serverIDs, room IDs, or aliases are preferred.autoJoin: can be"always","allowlist", or"off", with"off"as the default. This applies to every Matrix invite, including those styled as DMs.autoJoinAllowlist: lists rooms or aliases permitted whenautoJoinequals"allowlist". Alias entries resolve through the homeserver rather than against state reported by the invited room.contextVisibility: controls extra context visibility, defaulting to"all", with"allowlist"and"allowlist_quote"as alternatives.
Reply behavior
joinIntro: triggered upon the bot's entry into an approved group room. Standard setting:true. Can be overridden per account:accounts.<accountId>.joinIntro.replyToMode: options are"off"(the default),"first","all", or"batched".threadReplies: choose from"off"(unless explicitly configured, the top-level default points to"inbound"),"inbound", or"always".threadBindings: per-channel adjustments governing thread-bound session routing and its lifecycle.streaming: an embedded object{ mode, chunkMode, block: { enabled, coalesce }, preview: { toolProgress }, progress: { label, labels, maxLines, maxLineChars, toolProgress } }. The value ofmodecan be"off"(default),"partial","quiet", or"progress". Older scalar or boolean formats are converted viaopenclaw doctor --fix.streaming.block.enabled: withtrue, finished assistant blocks remain as individual progress messages. Default:false.markdown: optional Markdown rendering settings applied to outbound text.responsePrefix: an optional string added to the start of outbound replies.textChunkLimit: character-based chunk size for outbound traffic whenstreaming.chunkMode: "length"is active. Default:4000.streaming.chunkMode: either"length"(default, splits based on character count) or"newline"(breaks at line boundaries).historyLimit: how many recent room messages are supplied asInboundHistorywhen the agent is activated by a room message. If not set, falls back tomessages.groupChat.historyLimit; the effective default is0(turned off).mediaMaxMb: maximum media size in MB for outbound sends and inbound processing. Default:20.
Reaction settings
ackReaction: overrides the ack reaction for this channel or account.ackReactionScope: scope override (default is"group-mentions", with alternatives"group-all","direct","all","none", and"off").reactionNotifications: how inbound reaction notifications are handled (default"own", or"off").
Tooling and per-room overrides
actions: tool access control applied per action (messages,reactions,pins,profile,memberInfo,channelInfo,verification).groups: policy mapping based on individual rooms. After resolution, the stable room ID determines session identity. (roomsserves as a deprecated alternative.)groups.<room>.account: limit a single inherited room entry to one designated account.groups.<room>.enabled: room-specific switch. Whenfalseis active, the room is treated as absent from the map.groups.<room>.requireMention: room-level adjustment of the mention prerequisite set at the channel scope.groups.<room>.allowBots: room-level adjustment of the channel-wide option (trueor"mentions").groups.<room>.botLoopProtection: room-level adjustment for the budget guarding bot-to-bot loop prevention.groups.<room>.users: room-specific list of permitted senders.groups.<room>.tools: room-specific tool allow or deny exceptions.groups.<room>.autoReply: room-level mention gating adjustment.trueturns off mention requirements for that room;falsereactivates them.groups.<room>.skills: room-specific skill filtering.groups.<room>.systemPrompt: room-specific system prompt fragment.
Exec approval settings
execApprovals.enabled: route exec approvals via Matrix-native prompts.execApprovals.approvers: Matrix user IDs eligible to approve. If not set,dm.allowFromis used.execApprovals.target:"dm"(standard),"channel", or"both".execApprovals.agentFilter/execApprovals.sessionFilter: optional agent or session allowlists for delivery.
Related
- Channels Overview - every supported channel
- Pairing - DM authentication and the pairing process
- Groups - group chat behavior and mention gating
- Channel Routing - routing messages across sessions
- Security - access model and hardening measures