Files
openclaw/docs/gateway/configuration-reference.md

105 KiB
Raw Blame History

summary, title, read_when
summary title read_when
Gateway config reference for core OpenClaw keys, defaults, and links to dedicated subsystem references Configuration reference
You need exact field-level config semantics or defaults
You are validating channel, model, gateway, or tool config blocks

Core config reference for ~/.openclaw/openclaw.json. For a task-oriented overview, see Configuration.

This page covers the main OpenClaw config surfaces and links out when a subsystem has its own deeper reference. It does not try to inline every channel/plugin-owned command catalog or every deep memory/QMD knob on one page.

Code truth:

  • openclaw config schema prints the live JSON Schema used for validation and Control UI, with bundled/plugin/channel metadata merged in when available
  • config.schema.lookup returns one path-scoped schema node for drill-down tooling
  • pnpm config:docs:check / pnpm config:docs:gen validate the config-doc baseline hash against the current schema surface

Dedicated deep references:

  • Memory configuration reference for agents.defaults.memorySearch.*, memory.qmd.*, memory.citations, and dreaming config under plugins.entries.memory-core.config.dreaming
  • Slash Commands for the current built-in + bundled command catalog
  • owning channel/plugin pages for channel-specific command surfaces

Config format is JSON5 (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted.


Channels

Each channel starts automatically when its config section exists (unless enabled: false).

DM and group access

All channels support DM policies and group policies:

DM policy Behavior
pairing (default) Unknown senders get a one-time pairing code; owner must approve
allowlist Only senders in allowFrom (or paired allow store)
open Allow all inbound DMs (requires allowFrom: ["*"])
disabled Ignore all inbound DMs
Group policy Behavior
allowlist (default) Only groups matching the configured allowlist
open Bypass group allowlists (mention-gating still applies)
disabled Block all group/room messages
`channels.defaults.groupPolicy` sets the default when a provider's `groupPolicy` is unset. Pairing codes expire after 1 hour. Pending DM pairing requests are capped at **3 per channel**. If a provider block is missing entirely (`channels.` absent), runtime group policy falls back to `allowlist` (fail-closed) with a startup warning.

Channel model overrides

Use channels.modelByChannel to pin specific channel IDs to a model. Values accept provider/model or configured model aliases. The channel mapping applies when a session does not already have a model override (for example, set via /model).

{
  channels: {
    modelByChannel: {
      discord: {
        "123456789012345678": "anthropic/claude-opus-4-6",
      },
      slack: {
        C1234567890: "openai/gpt-4.1",
      },
      telegram: {
        "-1001234567890": "openai/gpt-4.1-mini",
        "-1001234567890:topic:99": "anthropic/claude-sonnet-4-6",
      },
    },
  },
}

Channel defaults and heartbeat

Use channels.defaults for shared group-policy and heartbeat behavior across providers:

{
  channels: {
    defaults: {
      groupPolicy: "allowlist", // open | allowlist | disabled
      contextVisibility: "all", // all | allowlist | allowlist_quote
      heartbeat: {
        showOk: false,
        showAlerts: true,
        useIndicator: true,
      },
    },
  },
}
  • channels.defaults.groupPolicy: fallback group policy when a provider-level groupPolicy is unset.
  • channels.defaults.contextVisibility: default supplemental context visibility mode for all channels. Values: all (default, include all quoted/thread/history context), allowlist (only include context from allowlisted senders), allowlist_quote (same as allowlist but keep explicit quote/reply context). Per-channel override: channels.<channel>.contextVisibility.
  • channels.defaults.heartbeat.showOk: include healthy channel statuses in heartbeat output.
  • channels.defaults.heartbeat.showAlerts: include degraded/error statuses in heartbeat output.
  • channels.defaults.heartbeat.useIndicator: render compact indicator-style heartbeat output.

WhatsApp

WhatsApp runs through the gateway's web channel (Baileys Web). It starts automatically when a linked session exists.

{
  channels: {
    whatsapp: {
      dmPolicy: "pairing", // pairing | allowlist | open | disabled
      allowFrom: ["+15555550123", "+447700900123"],
      textChunkLimit: 4000,
      chunkMode: "length", // length | newline
      mediaMaxMb: 50,
      sendReadReceipts: true, // blue ticks (false in self-chat mode)
      groups: {
        "*": { requireMention: true },
      },
      groupPolicy: "allowlist",
      groupAllowFrom: ["+15551234567"],
    },
  },
  web: {
    enabled: true,
    heartbeatSeconds: 60,
    reconnect: {
      initialMs: 2000,
      maxMs: 120000,
      factor: 1.4,
      jitter: 0.2,
      maxAttempts: 0,
    },
  },
}
{
  channels: {
    whatsapp: {
      accounts: {
        default: {},
        personal: {},
        biz: {
          // authDir: "~/.openclaw/credentials/whatsapp/biz",
        },
      },
    },
  },
}
  • Outbound commands default to account default if present; otherwise the first configured account id (sorted).
  • Optional channels.whatsapp.defaultAccount overrides that fallback default account selection when it matches a configured account id.
  • Legacy single-account Baileys auth dir is migrated by openclaw doctor into whatsapp/default.
  • Per-account overrides: channels.whatsapp.accounts.<id>.sendReadReceipts, channels.whatsapp.accounts.<id>.dmPolicy, channels.whatsapp.accounts.<id>.allowFrom.

Telegram

{
  channels: {
    telegram: {
      enabled: true,
      botToken: "your-bot-token",
      dmPolicy: "pairing",
      allowFrom: ["tg:123456789"],
      groups: {
        "*": { requireMention: true },
        "-1001234567890": {
          allowFrom: ["@admin"],
          systemPrompt: "Keep answers brief.",
          topics: {
            "99": {
              requireMention: false,
              skills: ["search"],
              systemPrompt: "Stay on topic.",
            },
          },
        },
      },
      customCommands: [
        { command: "backup", description: "Git backup" },
        { command: "generate", description: "Create an image" },
      ],
      historyLimit: 50,
      replyToMode: "first", // off | first | all | batched
      linkPreview: true,
      streaming: "partial", // off | partial | block | progress (default: off; opt in explicitly to avoid preview-edit rate limits)
      actions: { reactions: true, sendMessage: true },
      reactionNotifications: "own", // off | own | all
      mediaMaxMb: 100,
      retry: {
        attempts: 3,
        minDelayMs: 400,
        maxDelayMs: 30000,
        jitter: 0.1,
      },
      network: {
        autoSelectFamily: true,
        dnsResultOrder: "ipv4first",
      },
      proxy: "socks5://localhost:9050",
      webhookUrl: "https://example.com/telegram-webhook",
      webhookSecret: "secret",
      webhookPath: "/telegram-webhook",
    },
  },
}
  • Bot token: channels.telegram.botToken or channels.telegram.tokenFile (regular file only; symlinks rejected), with TELEGRAM_BOT_TOKEN as fallback for the default account.
  • Optional channels.telegram.defaultAccount overrides default account selection when it matches a configured account id.
  • In multi-account setups (2+ account ids), set an explicit default (channels.telegram.defaultAccount or channels.telegram.accounts.default) to avoid fallback routing; openclaw doctor warns when this is missing or invalid.
  • configWrites: false blocks Telegram-initiated config writes (supergroup ID migrations, /config set|unset).
  • Top-level bindings[] entries with type: "acp" configure persistent ACP bindings for forum topics (use canonical chatId:topic:topicId in match.peer.id). Field semantics are shared in ACP Agents.
  • Telegram stream previews use sendMessage + editMessageText (works in direct and group chats).
  • Retry policy: see Retry policy.

Discord

{
  channels: {
    discord: {
      enabled: true,
      token: "your-bot-token",
      mediaMaxMb: 100,
      allowBots: false,
      actions: {
        reactions: true,
        stickers: true,
        polls: true,
        permissions: true,
        messages: true,
        threads: true,
        pins: true,
        search: true,
        memberInfo: true,
        roleInfo: true,
        roles: false,
        channelInfo: true,
        voiceStatus: true,
        events: true,
        moderation: false,
      },
      replyToMode: "off", // off | first | all | batched
      dmPolicy: "pairing",
      allowFrom: ["1234567890", "123456789012345678"],
      dm: { enabled: true, groupEnabled: false, groupChannels: ["openclaw-dm"] },
      guilds: {
        "123456789012345678": {
          slug: "friends-of-openclaw",
          requireMention: false,
          ignoreOtherMentions: true,
          reactionNotifications: "own",
          users: ["987654321098765432"],
          channels: {
            general: { allow: true },
            help: {
              allow: true,
              requireMention: true,
              users: ["987654321098765432"],
              skills: ["docs"],
              systemPrompt: "Short answers only.",
            },
          },
        },
      },
      historyLimit: 20,
      textChunkLimit: 2000,
      chunkMode: "length", // length | newline
      streaming: "off", // off | partial | block | progress (progress maps to partial on Discord)
      maxLinesPerMessage: 17,
      ui: {
        components: {
          accentColor: "#5865F2",
        },
      },
      threadBindings: {
        enabled: true,
        idleHours: 24,
        maxAgeHours: 0,
        spawnSubagentSessions: false, // opt-in for sessions_spawn({ thread: true })
      },
      voice: {
        enabled: true,
        autoJoin: [
          {
            guildId: "123456789012345678",
            channelId: "234567890123456789",
          },
        ],
        daveEncryption: true,
        decryptionFailureTolerance: 24,
        tts: {
          provider: "openai",
          openai: { voice: "alloy" },
        },
      },
      execApprovals: {
        enabled: "auto", // true | false | "auto"
        approvers: ["987654321098765432"],
        agentFilter: ["default"],
        sessionFilter: ["discord:"],
        target: "dm", // dm | channel | both
        cleanupAfterResolve: false,
      },
      retry: {
        attempts: 3,
        minDelayMs: 500,
        maxDelayMs: 30000,
        jitter: 0.1,
      },
    },
  },
}
  • Token: channels.discord.token, with DISCORD_BOT_TOKEN as fallback for the default account.
  • Direct outbound calls that provide an explicit Discord token use that token for the call; account retry/policy settings still come from the selected account in the active runtime snapshot.
  • Optional channels.discord.defaultAccount overrides default account selection when it matches a configured account id.
  • Use user:<id> (DM) or channel:<id> (guild channel) for delivery targets; bare numeric IDs are rejected.
  • Guild slugs are lowercase with spaces replaced by -; channel keys use the slugged name (no #). Prefer guild IDs.
  • Bot-authored messages are ignored by default. allowBots: true enables them; use allowBots: "mentions" to only accept bot messages that mention the bot (own messages still filtered).
  • channels.discord.guilds.<id>.ignoreOtherMentions (and channel overrides) drops messages that mention another user or role but not the bot (excluding @everyone/@here).
  • maxLinesPerMessage (default 17) splits tall messages even when under 2000 chars.
  • channels.discord.threadBindings controls Discord thread-bound routing:
    • enabled: Discord override for thread-bound session features (/focus, /unfocus, /agents, /session idle, /session max-age, and bound delivery/routing)
    • idleHours: Discord override for inactivity auto-unfocus in hours (0 disables)
    • maxAgeHours: Discord override for hard max age in hours (0 disables)
    • spawnSubagentSessions: opt-in switch for sessions_spawn({ thread: true }) auto thread creation/binding
  • Top-level bindings[] entries with type: "acp" configure persistent ACP bindings for channels and threads (use channel/thread id in match.peer.id). Field semantics are shared in ACP Agents.
  • channels.discord.ui.components.accentColor sets the accent color for Discord components v2 containers.
  • channels.discord.voice enables Discord voice channel conversations and optional auto-join + TTS overrides.
  • channels.discord.voice.daveEncryption and channels.discord.voice.decryptionFailureTolerance pass through to @discordjs/voice DAVE options (true and 24 by default).
  • OpenClaw additionally attempts voice receive recovery by leaving/rejoining a voice session after repeated decrypt failures.
  • channels.discord.streaming is the canonical stream mode key. Legacy streamMode and boolean streaming values are auto-migrated.
  • channels.discord.autoPresence maps runtime availability to bot presence (healthy => online, degraded => idle, exhausted => dnd) and allows optional status text overrides.
  • channels.discord.dangerouslyAllowNameMatching re-enables mutable name/tag matching (break-glass compatibility mode).
  • channels.discord.execApprovals: Discord-native exec approval delivery and approver authorization.
    • enabled: true, false, or "auto" (default). In auto mode, exec approvals activate when approvers can be resolved from approvers or commands.ownerAllowFrom.
    • approvers: Discord user IDs allowed to approve exec requests. Falls back to commands.ownerAllowFrom when omitted.
    • agentFilter: optional agent ID allowlist. Omit to forward approvals for all agents.
    • sessionFilter: optional session key patterns (substring or regex).
    • target: where to send approval prompts. "dm" (default) sends to approver DMs, "channel" sends to the originating channel, "both" sends to both. When target includes "channel", buttons are only usable by resolved approvers.
    • cleanupAfterResolve: when true, deletes approval DMs after approval, denial, or timeout.

Reaction notification modes: off (none), own (bot's messages, default), all (all messages), allowlist (from guilds.<id>.users on all messages).

Google Chat

{
  channels: {
    googlechat: {
      enabled: true,
      serviceAccountFile: "/path/to/service-account.json",
      audienceType: "app-url", // app-url | project-number
      audience: "https://gateway.example.com/googlechat",
      webhookPath: "/googlechat",
      botUser: "users/1234567890",
      dm: {
        enabled: true,
        policy: "pairing",
        allowFrom: ["users/1234567890"],
      },
      groupPolicy: "allowlist",
      groups: {
        "spaces/AAAA": { allow: true, requireMention: true },
      },
      actions: { reactions: true },
      typingIndicator: "message",
      mediaMaxMb: 20,
    },
  },
}
  • Service account JSON: inline (serviceAccount) or file-based (serviceAccountFile).
  • Service account SecretRef is also supported (serviceAccountRef).
  • Env fallbacks: GOOGLE_CHAT_SERVICE_ACCOUNT or GOOGLE_CHAT_SERVICE_ACCOUNT_FILE.
  • Use spaces/<spaceId> or users/<userId> for delivery targets.
  • channels.googlechat.dangerouslyAllowNameMatching re-enables mutable email principal matching (break-glass compatibility mode).

Slack

{
  channels: {
    slack: {
      enabled: true,
      botToken: "xoxb-...",
      appToken: "xapp-...",
      dmPolicy: "pairing",
      allowFrom: ["U123", "U456", "*"],
      dm: { enabled: true, groupEnabled: false, groupChannels: ["G123"] },
      channels: {
        C123: { allow: true, requireMention: true, allowBots: false },
        "#general": {
          allow: true,
          requireMention: true,
          allowBots: false,
          users: ["U123"],
          skills: ["docs"],
          systemPrompt: "Short answers only.",
        },
      },
      historyLimit: 50,
      allowBots: false,
      reactionNotifications: "own",
      reactionAllowlist: ["U123"],
      replyToMode: "off", // off | first | all | batched
      thread: {
        historyScope: "thread", // thread | channel
        inheritParent: false,
      },
      actions: {
        reactions: true,
        messages: true,
        pins: true,
        memberInfo: true,
        emojiList: true,
      },
      slashCommand: {
        enabled: true,
        name: "openclaw",
        sessionPrefix: "slack:slash",
        ephemeral: true,
      },
      typingReaction: "hourglass_flowing_sand",
      textChunkLimit: 4000,
      chunkMode: "length",
      streaming: {
        mode: "partial", // off | partial | block | progress
        nativeTransport: true, // use Slack native streaming API when mode=partial
      },
      mediaMaxMb: 20,
      execApprovals: {
        enabled: "auto", // true | false | "auto"
        approvers: ["U123"],
        agentFilter: ["default"],
        sessionFilter: ["slack:"],
        target: "dm", // dm | channel | both
      },
    },
  },
}
  • Socket mode requires both botToken and appToken (SLACK_BOT_TOKEN + SLACK_APP_TOKEN for default account env fallback).
  • HTTP mode requires botToken plus signingSecret (at root or per-account).
  • botToken, appToken, signingSecret, and userToken accept plaintext strings or SecretRef objects.
  • Slack account snapshots expose per-credential source/status fields such as botTokenSource, botTokenStatus, appTokenStatus, and, in HTTP mode, signingSecretStatus. configured_unavailable means the account is configured through SecretRef but the current command/runtime path could not resolve the secret value.
  • configWrites: false blocks Slack-initiated config writes.
  • Optional channels.slack.defaultAccount overrides default account selection when it matches a configured account id.
  • channels.slack.streaming.mode is the canonical Slack stream mode key. channels.slack.streaming.nativeTransport controls Slack's native streaming transport. Legacy streamMode, boolean streaming, and nativeStreaming values are auto-migrated.
  • Use user:<id> (DM) or channel:<id> for delivery targets.

Reaction notification modes: off, own (default), all, allowlist (from reactionAllowlist).

Thread session isolation: thread.historyScope is per-thread (default) or shared across channel. thread.inheritParent copies parent channel transcript to new threads.

  • Slack native streaming plus the Slack assistant-style "is typing..." thread status require a reply thread target. Top-level DMs stay off-thread by default, so they use typingReaction or normal delivery instead of the thread-style preview.
  • typingReaction adds a temporary reaction to the inbound Slack message while a reply is running, then removes it on completion. Use a Slack emoji shortcode such as "hourglass_flowing_sand".
  • channels.slack.execApprovals: Slack-native exec approval delivery and approver authorization. Same schema as Discord: enabled (true/false/"auto"), approvers (Slack user IDs), agentFilter, sessionFilter, and target ("dm", "channel", or "both").
Action group Default Notes
reactions enabled React + list reactions
messages enabled Read/send/edit/delete
pins enabled Pin/unpin/list
memberInfo enabled Member info
emojiList enabled Custom emoji list

Mattermost

Mattermost ships as a plugin: openclaw plugins install @openclaw/mattermost.

{
  channels: {
    mattermost: {
      enabled: true,
      botToken: "mm-token",
      baseUrl: "https://chat.example.com",
      dmPolicy: "pairing",
      chatmode: "oncall", // oncall | onmessage | onchar
      oncharPrefixes: [">", "!"],
      groups: {
        "*": { requireMention: true },
        "team-channel-id": { requireMention: false },
      },
      commands: {
        native: true, // opt-in
        nativeSkills: true,
        callbackPath: "/api/channels/mattermost/command",
        // Optional explicit URL for reverse-proxy/public deployments
        callbackUrl: "https://gateway.example.com/api/channels/mattermost/command",
      },
      textChunkLimit: 4000,
      chunkMode: "length",
    },
  },
}

Chat modes: oncall (respond on @-mention, default), onmessage (every message), onchar (messages starting with trigger prefix).

When Mattermost native commands are enabled:

  • commands.callbackPath must be a path (for example /api/channels/mattermost/command), not a full URL.
  • commands.callbackUrl must resolve to the OpenClaw gateway endpoint and be reachable from the Mattermost server.
  • Native slash callbacks are authenticated with the per-command tokens returned by Mattermost during slash command registration. If registration fails or no commands are activated, OpenClaw rejects callbacks with Unauthorized: invalid command token.
  • For private/tailnet/internal callback hosts, Mattermost may require ServiceSettings.AllowedUntrustedInternalConnections to include the callback host/domain. Use host/domain values, not full URLs.
  • channels.mattermost.configWrites: allow or deny Mattermost-initiated config writes.
  • channels.mattermost.requireMention: require @mention before replying in channels.
  • channels.mattermost.groups.<channelId>.requireMention: per-channel mention-gating override ("*" for default).
  • Optional channels.mattermost.defaultAccount overrides default account selection when it matches a configured account id.

Signal

{
  channels: {
    signal: {
      enabled: true,
      account: "+15555550123", // optional account binding
      dmPolicy: "pairing",
      allowFrom: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
      configWrites: true,
      reactionNotifications: "own", // off | own | all | allowlist
      reactionAllowlist: ["+15551234567", "uuid:123e4567-e89b-12d3-a456-426614174000"],
      historyLimit: 50,
    },
  },
}

Reaction notification modes: off, own (default), all, allowlist (from reactionAllowlist).

  • channels.signal.account: pin channel startup to a specific Signal account identity.
  • channels.signal.configWrites: allow or deny Signal-initiated config writes.
  • Optional channels.signal.defaultAccount overrides default account selection when it matches a configured account id.

BlueBubbles

BlueBubbles is the recommended iMessage path (plugin-backed, configured under channels.bluebubbles).

{
  channels: {
    bluebubbles: {
      enabled: true,
      dmPolicy: "pairing",
      // serverUrl, password, webhookPath, group controls, and advanced actions:
      // see /channels/bluebubbles
    },
  },
}
  • Core key paths covered here: channels.bluebubbles, channels.bluebubbles.dmPolicy.
  • Optional channels.bluebubbles.defaultAccount overrides default account selection when it matches a configured account id.
  • Top-level bindings[] entries with type: "acp" can bind BlueBubbles conversations to persistent ACP sessions. Use a BlueBubbles handle or target string (chat_id:*, chat_guid:*, chat_identifier:*) in match.peer.id. Shared field semantics: ACP Agents.
  • Full BlueBubbles channel configuration is documented in BlueBubbles.

iMessage

OpenClaw spawns imsg rpc (JSON-RPC over stdio). No daemon or port required.

{
  channels: {
    imessage: {
      enabled: true,
      cliPath: "imsg",
      dbPath: "~/Library/Messages/chat.db",
      remoteHost: "user@gateway-host",
      dmPolicy: "pairing",
      allowFrom: ["+15555550123", "user@example.com", "chat_id:123"],
      historyLimit: 50,
      includeAttachments: false,
      attachmentRoots: ["/Users/*/Library/Messages/Attachments"],
      remoteAttachmentRoots: ["/Users/*/Library/Messages/Attachments"],
      mediaMaxMb: 16,
      service: "auto",
      region: "US",
    },
  },
}
  • Optional channels.imessage.defaultAccount overrides default account selection when it matches a configured account id.

  • Requires Full Disk Access to the Messages DB.

  • Prefer chat_id:<id> targets. Use imsg chats --limit 20 to list chats.

  • cliPath can point to an SSH wrapper; set remoteHost (host or user@host) for SCP attachment fetching.

  • attachmentRoots and remoteAttachmentRoots restrict inbound attachment paths (default: /Users/*/Library/Messages/Attachments).

  • SCP uses strict host-key checking, so ensure the relay host key already exists in ~/.ssh/known_hosts.

  • channels.imessage.configWrites: allow or deny iMessage-initiated config writes.

  • Top-level bindings[] entries with type: "acp" can bind iMessage conversations to persistent ACP sessions. Use a normalized handle or explicit chat target (chat_id:*, chat_guid:*, chat_identifier:*) in match.peer.id. Shared field semantics: ACP Agents.

#!/usr/bin/env bash
exec ssh -T gateway-host imsg "$@"

Matrix

Matrix is plugin-backed and configured under channels.matrix.

{
  channels: {
    matrix: {
      enabled: true,
      homeserver: "https://matrix.example.org",
      accessToken: "syt_bot_xxx",
      proxy: "http://127.0.0.1:7890",
      encryption: true,
      initialSyncLimit: 20,
      defaultAccount: "ops",
      accounts: {
        ops: {
          name: "Ops",
          userId: "@ops:example.org",
          accessToken: "syt_ops_xxx",
        },
        alerts: {
          userId: "@alerts:example.org",
          password: "secret",
          proxy: "http://127.0.0.1:7891",
        },
      },
    },
  },
}
  • Token auth uses accessToken; password auth uses userId + password.
  • channels.matrix.proxy routes Matrix HTTP traffic through an explicit HTTP(S) proxy. Named accounts can override it with channels.matrix.accounts.<id>.proxy.
  • channels.matrix.network.dangerouslyAllowPrivateNetwork allows private/internal homeservers. proxy and this network opt-in are independent controls.
  • channels.matrix.defaultAccount selects the preferred account in multi-account setups.
  • channels.matrix.autoJoin defaults to off, so invited rooms and fresh DM-style invites are ignored until you set autoJoin: "allowlist" with autoJoinAllowlist or autoJoin: "always".
  • channels.matrix.execApprovals: Matrix-native exec approval delivery and approver authorization.
    • enabled: true, false, or "auto" (default). In auto mode, exec approvals activate when approvers can be resolved from approvers or commands.ownerAllowFrom.
    • approvers: Matrix user IDs (e.g. @owner:example.org) allowed to approve exec requests.
    • agentFilter: optional agent ID allowlist. Omit to forward approvals for all agents.
    • sessionFilter: optional session key patterns (substring or regex).
    • target: where to send approval prompts. "dm" (default), "channel" (originating room), or "both".
    • Per-account overrides: channels.matrix.accounts.<id>.execApprovals.
  • channels.matrix.dm.sessionScope controls how Matrix DMs group into sessions: per-user (default) shares by routed peer, while per-room isolates each DM room.
  • Matrix status probes and live directory lookups use the same proxy policy as runtime traffic.
  • Full Matrix configuration, targeting rules, and setup examples are documented in Matrix.

Microsoft Teams

Microsoft Teams is plugin-backed and configured under channels.msteams.

{
  channels: {
    msteams: {
      enabled: true,
      configWrites: true,
      // appId, appPassword, tenantId, webhook, team/channel policies:
      // see /channels/msteams
    },
  },
}
  • Core key paths covered here: channels.msteams, channels.msteams.configWrites.
  • Full Teams config (credentials, webhook, DM/group policy, per-team/per-channel overrides) is documented in Microsoft Teams.

IRC

IRC is plugin-backed and configured under channels.irc.

{
  channels: {
    irc: {
      enabled: true,
      dmPolicy: "pairing",
      configWrites: true,
      nickserv: {
        enabled: true,
        service: "NickServ",
        password: "${IRC_NICKSERV_PASSWORD}",
        register: false,
        registerEmail: "bot@example.com",
      },
    },
  },
}
  • Core key paths covered here: channels.irc, channels.irc.dmPolicy, channels.irc.configWrites, channels.irc.nickserv.*.
  • Optional channels.irc.defaultAccount overrides default account selection when it matches a configured account id.
  • Full IRC channel configuration (host/port/TLS/channels/allowlists/mention gating) is documented in IRC.

Multi-account (all channels)

Run multiple accounts per channel (each with its own accountId):

{
  channels: {
    telegram: {
      accounts: {
        default: {
          name: "Primary bot",
          botToken: "123456:ABC...",
        },
        alerts: {
          name: "Alerts bot",
          botToken: "987654:XYZ...",
        },
      },
    },
  },
}
  • default is used when accountId is omitted (CLI + routing).
  • Env tokens only apply to the default account.
  • Base channel settings apply to all accounts unless overridden per account.
  • Use bindings[].match.accountId to route each account to a different agent.
  • If you add a non-default account via openclaw channels add (or channel onboarding) while still on a single-account top-level channel config, OpenClaw promotes account-scoped top-level single-account values into the channel account map first so the original account keeps working. Most channels move them into channels.<channel>.accounts.default; Matrix can preserve an existing matching named/default target instead.
  • Existing channel-only bindings (no accountId) keep matching the default account; account-scoped bindings remain optional.
  • openclaw doctor --fix also repairs mixed shapes by moving account-scoped top-level single-account values into the promoted account chosen for that channel. Most channels use accounts.default; Matrix can preserve an existing matching named/default target instead.

Other plugin channels

Many plugin channels are configured as channels.<id> and documented in their dedicated channel pages (for example Feishu, Matrix, LINE, Nostr, Zalo, Nextcloud Talk, Synology Chat, and Twitch). See the full channel index: Channels.

Group chat mention gating

Group messages default to require mention (metadata mention or safe regex patterns). Applies to WhatsApp, Telegram, Discord, Google Chat, and iMessage group chats.

Mention types:

  • Metadata mentions: Native platform @-mentions. Ignored in WhatsApp self-chat mode.
  • Text patterns: Safe regex patterns in agents.list[].groupChat.mentionPatterns. Invalid patterns and unsafe nested repetition are ignored.
  • Mention gating is enforced only when detection is possible (native mentions or at least one pattern).
{
  messages: {
    groupChat: { historyLimit: 50 },
  },
  agents: {
    list: [{ id: "main", groupChat: { mentionPatterns: ["@openclaw", "openclaw"] } }],
  },
}

messages.groupChat.historyLimit sets the global default. Channels can override with channels.<channel>.historyLimit (or per-account). Set 0 to disable.

DM history limits

{
  channels: {
    telegram: {
      dmHistoryLimit: 30,
      dms: {
        "123456789": { historyLimit: 50 },
      },
    },
  },
}

Resolution: per-DM override → provider default → no limit (all retained).

Supported: telegram, whatsapp, discord, slack, signal, imessage, msteams.

Self-chat mode

Include your own number in allowFrom to enable self-chat mode (ignores native @-mentions, only responds to text patterns):

{
  channels: {
    whatsapp: {
      allowFrom: ["+15555550123"],
      groups: { "*": { requireMention: true } },
    },
  },
  agents: {
    list: [
      {
        id: "main",
        groupChat: { mentionPatterns: ["reisponde", "@openclaw"] },
      },
    ],
  },
}

Commands (chat command handling)

{
  commands: {
    native: "auto", // register native commands when supported
    nativeSkills: "auto", // register native skill commands when supported
    text: true, // parse /commands in chat messages
    bash: false, // allow ! (alias: /bash)
    bashForegroundMs: 2000,
    config: false, // allow /config
    mcp: false, // allow /mcp
    plugins: false, // allow /plugins
    debug: false, // allow /debug
    restart: true, // allow /restart + gateway restart tool
    ownerAllowFrom: ["discord:123456789012345678"],
    ownerDisplay: "raw", // raw | hash
    ownerDisplaySecret: "${OWNER_ID_HASH_SECRET}",
    allowFrom: {
      "*": ["user1"],
      discord: ["user:123"],
    },
    useAccessGroups: true,
  },
}
  • This block configures command surfaces. For the current built-in + bundled command catalog, see Slash Commands.
  • This page is a config-key reference, not the full command catalog. Channel/plugin-owned commands such as QQ Bot /bot-ping /bot-help /bot-logs, LINE /card, device-pair /pair, memory /dreaming, phone-control /phone, and Talk /voice are documented in their channel/plugin pages plus Slash Commands.
  • Text commands must be standalone messages with leading /.
  • native: "auto" turns on native commands for Discord/Telegram, leaves Slack off.
  • nativeSkills: "auto" turns on native skill commands for Discord/Telegram, leaves Slack off.
  • Override per channel: channels.discord.commands.native (bool or "auto"). false clears previously registered commands.
  • Override native skill registration per channel with channels.<provider>.commands.nativeSkills.
  • channels.telegram.customCommands adds extra Telegram bot menu entries.
  • bash: true enables ! <cmd> for host shell. Requires tools.elevated.enabled and sender in tools.elevated.allowFrom.<channel>.
  • config: true enables /config (reads/writes openclaw.json). For gateway chat.send clients, persistent /config set|unset writes also require operator.admin; read-only /config show stays available to normal write-scoped operator clients.
  • mcp: true enables /mcp for OpenClaw-managed MCP server config under mcp.servers.
  • plugins: true enables /plugins for plugin discovery, install, and enable/disable controls.
  • channels.<provider>.configWrites gates config mutations per channel (default: true).
  • For multi-account channels, channels.<provider>.accounts.<id>.configWrites also gates writes that target that account (for example /allowlist --config --account <id> or /config set channels.<provider>.accounts.<id>...).
  • restart: false disables /restart and gateway restart tool actions. Default: true.
  • ownerAllowFrom is the explicit owner allowlist for owner-only commands/tools. It is separate from allowFrom.
  • ownerDisplay: "hash" hashes owner ids in the system prompt. Set ownerDisplaySecret to control hashing.
  • allowFrom is per-provider. When set, it is the only authorization source (channel allowlists/pairing and useAccessGroups are ignored).
  • useAccessGroups: false allows commands to bypass access-group policies when allowFrom is not set.
  • Command docs map:

Agent defaults, multi-agent, sessions, and messages

Moved to a dedicated page — see Configuration — agents for:

  • agents.defaults.* (workspace, model, thinking, heartbeat, memory, media, skills, sandbox)
  • multiAgent.* (multi-agent routing and bindings)
  • session.* (session lifecycle, compaction, pruning)
  • messages.* (message delivery, TTS, markdown rendering)
  • talk.* (Talk mode)

Tools

Tool profiles

tools.profile sets a base allowlist before tools.allow/tools.deny:

Local onboarding defaults new local configs to tools.profile: "coding" when unset (existing explicit profiles are preserved).

Profile Includes
minimal session_status only
coding group:fs, group:runtime, group:web, group:sessions, group:memory, cron, image, image_generate, video_generate
messaging group:messaging, sessions_list, sessions_history, sessions_send, session_status
full No restriction (same as unset)

Tool groups

Group Tools
group:runtime exec, process, code_execution (bash is accepted as an alias for exec)
group:fs read, write, edit, apply_patch
group:sessions sessions_list, sessions_history, sessions_send, sessions_spawn, sessions_yield, subagents, session_status
group:memory memory_search, memory_get
group:web web_search, x_search, web_fetch
group:ui browser, canvas
group:automation cron, gateway
group:messaging message
group:nodes nodes
group:agents agents_list
group:media image, image_generate, video_generate, tts
group:openclaw All built-in tools (excludes provider plugins)

tools.allow / tools.deny

Global tool allow/deny policy (deny wins). Case-insensitive, supports * wildcards. Applied even when Docker sandbox is off.

{
  tools: { deny: ["browser", "canvas"] },
}

tools.byProvider

Further restrict tools for specific providers or models. Order: base profile → provider profile → allow/deny.

{
  tools: {
    profile: "coding",
    byProvider: {
      "google-antigravity": { profile: "minimal" },
      "openai/gpt-5.4": { allow: ["group:fs", "sessions_list"] },
    },
  },
}

tools.elevated

Controls elevated exec access outside the sandbox:

{
  tools: {
    elevated: {
      enabled: true,
      allowFrom: {
        whatsapp: ["+15555550123"],
        discord: ["1234567890123", "987654321098765432"],
      },
    },
  },
}
  • Per-agent override (agents.list[].tools.elevated) can only further restrict.
  • /elevated on|off|ask|full stores state per session; inline directives apply to single message.
  • Elevated exec bypasses sandboxing and uses the configured escape path (gateway by default, or node when the exec target is node).

tools.exec

{
  tools: {
    exec: {
      backgroundMs: 10000,
      timeoutSec: 1800,
      cleanupMs: 1800000,
      notifyOnExit: true,
      notifyOnExitEmptySuccess: false,
      applyPatch: {
        enabled: false,
        allowModels: ["gpt-5.5"],
      },
    },
  },
}

tools.loopDetection

Tool-loop safety checks are disabled by default. Set enabled: true to activate detection. Settings can be defined globally in tools.loopDetection and overridden per-agent at agents.list[].tools.loopDetection.

{
  tools: {
    loopDetection: {
      enabled: true,
      historySize: 30,
      warningThreshold: 10,
      criticalThreshold: 20,
      globalCircuitBreakerThreshold: 30,
      detectors: {
        genericRepeat: true,
        knownPollNoProgress: true,
        pingPong: true,
      },
    },
  },
}
  • historySize: max tool-call history retained for loop analysis.
  • warningThreshold: repeating no-progress pattern threshold for warnings.
  • criticalThreshold: higher repeating threshold for blocking critical loops.
  • globalCircuitBreakerThreshold: hard stop threshold for any no-progress run.
  • detectors.genericRepeat: warn on repeated same-tool/same-args calls.
  • detectors.knownPollNoProgress: warn/block on known poll tools (process.poll, command_status, etc.).
  • detectors.pingPong: warn/block on alternating no-progress pair patterns.
  • If warningThreshold >= criticalThreshold or criticalThreshold >= globalCircuitBreakerThreshold, validation fails.

tools.web

{
  tools: {
    web: {
      search: {
        enabled: true,
        apiKey: "brave_api_key", // or BRAVE_API_KEY env
        maxResults: 5,
        timeoutSeconds: 30,
        cacheTtlMinutes: 15,
      },
      fetch: {
        enabled: true,
        provider: "firecrawl", // optional; omit for auto-detect
        maxChars: 50000,
        maxCharsCap: 50000,
        maxResponseBytes: 2000000,
        timeoutSeconds: 30,
        cacheTtlMinutes: 15,
        maxRedirects: 3,
        readability: true,
        userAgent: "custom-ua",
      },
    },
  },
}

tools.media

Configures inbound media understanding (image/audio/video):

{
  tools: {
    media: {
      concurrency: 2,
      asyncCompletion: {
        directSend: false, // opt-in: send finished async music/video directly to the channel
      },
      audio: {
        enabled: true,
        maxBytes: 20971520,
        scope: {
          default: "deny",
          rules: [{ action: "allow", match: { chatType: "direct" } }],
        },
        models: [
          { provider: "openai", model: "gpt-4o-mini-transcribe" },
          { type: "cli", command: "whisper", args: ["--model", "base", "{{MediaPath}}"] },
        ],
      },
      video: {
        enabled: true,
        maxBytes: 52428800,
        models: [{ provider: "google", model: "gemini-3-flash-preview" }],
      },
    },
  },
}

Provider entry (type: "provider" or omitted):

  • provider: API provider id (openai, anthropic, google/gemini, groq, etc.)
  • model: model id override
  • profile / preferredProfile: auth-profiles.json profile selection

CLI entry (type: "cli"):

  • command: executable to run
  • args: templated args (supports {{MediaPath}}, {{Prompt}}, {{MaxChars}}, etc.)

Common fields:

  • capabilities: optional list (image, audio, video). Defaults: openai/anthropic/minimax → image, google → image+audio+video, groq → audio.
  • prompt, maxChars, maxBytes, timeoutSeconds, language: per-entry overrides.
  • Failures fall back to the next entry.

Provider auth follows standard order: auth-profiles.json → env vars → models.providers.*.apiKey.

Async completion fields:

  • asyncCompletion.directSend: when true, completed async music_generate and video_generate tasks try direct channel delivery first. Default: false (legacy requester-session wake/model-delivery path).

tools.agentToAgent

{
  tools: {
    agentToAgent: {
      enabled: false,
      allow: ["home", "work"],
    },
  },
}

tools.sessions

Controls which sessions can be targeted by the session tools (sessions_list, sessions_history, sessions_send).

Default: tree (current session + sessions spawned by it, such as subagents).

{
  tools: {
    sessions: {
      // "self" | "tree" | "agent" | "all"
      visibility: "tree",
    },
  },
}

Notes:

  • self: only the current session key.
  • tree: current session + sessions spawned by the current session (subagents).
  • agent: any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id).
  • all: any session. Cross-agent targeting still requires tools.agentToAgent.
  • Sandbox clamp: when the current session is sandboxed and agents.defaults.sandbox.sessionToolsVisibility="spawned", visibility is forced to tree even if tools.sessions.visibility="all".

tools.sessions_spawn

Controls inline attachment support for sessions_spawn.

{
  tools: {
    sessions_spawn: {
      attachments: {
        enabled: false, // opt-in: set true to allow inline file attachments
        maxTotalBytes: 5242880, // 5 MB total across all files
        maxFiles: 50,
        maxFileBytes: 1048576, // 1 MB per file
        retainOnSessionKeep: false, // keep attachments when cleanup="keep"
      },
    },
  },
}

Notes:

  • Attachments are only supported for runtime: "subagent". ACP runtime rejects them.
  • Files are materialized into the child workspace at .openclaw/attachments/<uuid>/ with a .manifest.json.
  • Attachment content is automatically redacted from transcript persistence.
  • Base64 inputs are validated with strict alphabet/padding checks and a pre-decode size guard.
  • File permissions are 0700 for directories and 0600 for files.
  • Cleanup follows the cleanup policy: delete always removes attachments; keep retains them only when retainOnSessionKeep: true.

tools.experimental

Experimental built-in tool flags. Default off unless a strict-agentic GPT-5 auto-enable rule applies.

{
  tools: {
    experimental: {
      planTool: true, // enable experimental update_plan
    },
  },
}

Notes:

  • planTool: enables the structured update_plan tool for non-trivial multi-step work tracking.
  • Default: false unless agents.defaults.embeddedPi.executionContract (or a per-agent override) is set to "strict-agentic" for an OpenAI or OpenAI Codex GPT-5-family run. Set true to force the tool on outside that scope, or false to keep it off even for strict-agentic GPT-5 runs.
  • When enabled, the system prompt also adds usage guidance so the model only uses it for substantial work and keeps at most one step in_progress.

agents.defaults.subagents

{
  agents: {
    defaults: {
      subagents: {
        allowAgents: ["research"],
        model: "minimax/MiniMax-M2.7",
        maxConcurrent: 8,
        runTimeoutSeconds: 900,
        archiveAfterMinutes: 60,
      },
    },
  },
}
  • model: default model for spawned sub-agents. If omitted, sub-agents inherit the caller's model.
  • allowAgents: default allowlist of target agent ids for sessions_spawn when the requester agent does not set its own subagents.allowAgents (["*"] = any; default: same agent only).
  • runTimeoutSeconds: default timeout (seconds) for sessions_spawn when the tool call omits runTimeoutSeconds. 0 means no timeout.
  • Per-subagent tool policy: tools.subagents.tools.allow / tools.subagents.tools.deny.

Custom providers and base URLs

OpenClaw uses the built-in model catalog. Add custom providers via models.providers in config or ~/.openclaw/agents/<agentId>/agent/models.json.

{
  models: {
    mode: "merge", // merge (default) | replace
    providers: {
      "custom-proxy": {
        baseUrl: "http://localhost:4000/v1",
        apiKey: "LITELLM_KEY",
        api: "openai-completions", // openai-completions | openai-responses | anthropic-messages | google-generative-ai
        models: [
          {
            id: "llama-3.1-8b",
            name: "Llama 3.1 8B",
            reasoning: false,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 128000,
            contextTokens: 96000,
            maxTokens: 32000,
          },
        ],
      },
    },
  },
}
  • Use authHeader: true + headers for custom auth needs.
  • Override agent config root with OPENCLAW_AGENT_DIR (or PI_CODING_AGENT_DIR, a legacy environment variable alias).
  • Merge precedence for matching provider IDs:
    • Non-empty agent models.json baseUrl values win.
    • Non-empty agent apiKey values win only when that provider is not SecretRef-managed in current config/auth-profile context.
    • SecretRef-managed provider apiKey values are refreshed from source markers (ENV_VAR_NAME for env refs, secretref-managed for file/exec refs) instead of persisting resolved secrets.
    • SecretRef-managed provider header values are refreshed from source markers (secretref-env:ENV_VAR_NAME for env refs, secretref-managed for file/exec refs).
    • Empty or missing agent apiKey/baseUrl fall back to models.providers in config.
    • Matching model contextWindow/maxTokens use the higher value between explicit config and implicit catalog values.
    • Matching model contextTokens preserves an explicit runtime cap when present; use it to limit effective context without changing native model metadata.
    • Use models.mode: "replace" when you want config to fully rewrite models.json.
    • Marker persistence is source-authoritative: markers are written from the active source config snapshot (pre-resolution), not from resolved runtime secret values.

Provider field details

  • models.mode: provider catalog behavior (merge or replace).
  • models.providers: custom provider map keyed by provider id.
    • Safe edits: use openclaw config set models.providers.<id> '<json>' --strict-json --merge or openclaw config set models.providers.<id>.models '<json-array>' --strict-json --merge for additive updates. config set refuses destructive replacements unless you pass --replace.
  • models.providers.*.api: request adapter (openai-completions, openai-responses, anthropic-messages, google-generative-ai, etc).
  • models.providers.*.apiKey: provider credential (prefer SecretRef/env substitution).
  • models.providers.*.auth: auth strategy (api-key, token, oauth, aws-sdk).
  • models.providers.*.injectNumCtxForOpenAICompat: for Ollama + openai-completions, inject options.num_ctx into requests (default: true).
  • models.providers.*.authHeader: force credential transport in the Authorization header when required.
  • models.providers.*.baseUrl: upstream API base URL.
  • models.providers.*.headers: extra static headers for proxy/tenant routing.
  • models.providers.*.request: transport overrides for model-provider HTTP requests.
    • request.headers: extra headers (merged with provider defaults). Values accept SecretRef.
    • request.auth: auth strategy override. Modes: "provider-default" (use provider's built-in auth), "authorization-bearer" (with token), "header" (with headerName, value, optional prefix).
    • request.proxy: HTTP proxy override. Modes: "env-proxy" (use HTTP_PROXY/HTTPS_PROXY env vars), "explicit-proxy" (with url). Both modes accept an optional tls sub-object.
    • request.tls: TLS override for direct connections. Fields: ca, cert, key, passphrase (all accept SecretRef), serverName, insecureSkipVerify.
    • request.allowPrivateNetwork: when true, allow HTTPS to baseUrl when DNS resolves to private, CGNAT, or similar ranges, via the provider HTTP fetch guard (operator opt-in for trusted self-hosted OpenAI-compatible endpoints). WebSocket uses the same request for headers/TLS but not that fetch SSRF gate. Default false.
  • models.providers.*.models: explicit provider model catalog entries.
  • models.providers.*.models.*.contextWindow: native model context window metadata.
  • models.providers.*.models.*.contextTokens: optional runtime context cap. Use this when you want a smaller effective context budget than the model's native contextWindow.
  • models.providers.*.models.*.compat.supportsDeveloperRole: optional compatibility hint. For api: "openai-completions" with a non-empty non-native baseUrl (host not api.openai.com), OpenClaw forces this to false at runtime. Empty/omitted baseUrl keeps default OpenAI behavior.
  • models.providers.*.models.*.compat.requiresStringContent: optional compatibility hint for string-only OpenAI-compatible chat endpoints. When true, OpenClaw flattens pure text messages[].content arrays into plain strings before sending the request.
  • plugins.entries.amazon-bedrock.config.discovery: Bedrock auto-discovery settings root.
  • plugins.entries.amazon-bedrock.config.discovery.enabled: turn implicit discovery on/off.
  • plugins.entries.amazon-bedrock.config.discovery.region: AWS region for discovery.
  • plugins.entries.amazon-bedrock.config.discovery.providerFilter: optional provider-id filter for targeted discovery.
  • plugins.entries.amazon-bedrock.config.discovery.refreshInterval: polling interval for discovery refresh.
  • plugins.entries.amazon-bedrock.config.discovery.defaultContextWindow: fallback context window for discovered models.
  • plugins.entries.amazon-bedrock.config.discovery.defaultMaxTokens: fallback max output tokens for discovered models.

Provider examples

{
  env: { CEREBRAS_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: {
        primary: "cerebras/zai-glm-4.7",
        fallbacks: ["cerebras/zai-glm-4.6"],
      },
      models: {
        "cerebras/zai-glm-4.7": { alias: "GLM 4.7 (Cerebras)" },
        "cerebras/zai-glm-4.6": { alias: "GLM 4.6 (Cerebras)" },
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      cerebras: {
        baseUrl: "https://api.cerebras.ai/v1",
        apiKey: "${CEREBRAS_API_KEY}",
        api: "openai-completions",
        models: [
          { id: "zai-glm-4.7", name: "GLM 4.7 (Cerebras)" },
          { id: "zai-glm-4.6", name: "GLM 4.6 (Cerebras)" },
        ],
      },
    },
  },
}

Use cerebras/zai-glm-4.7 for Cerebras; zai/glm-4.7 for Z.AI direct.

{
  agents: {
    defaults: {
      model: { primary: "opencode/claude-opus-4-6" },
      models: { "opencode/claude-opus-4-6": { alias: "Opus" } },
    },
  },
}

Set OPENCODE_API_KEY (or OPENCODE_ZEN_API_KEY). Use opencode/... refs for the Zen catalog or opencode-go/... refs for the Go catalog. Shortcut: openclaw onboard --auth-choice opencode-zen or openclaw onboard --auth-choice opencode-go.

{
  agents: {
    defaults: {
      model: { primary: "zai/glm-4.7" },
      models: { "zai/glm-4.7": {} },
    },
  },
}

Set ZAI_API_KEY. z.ai/* and z-ai/* are accepted aliases. Shortcut: openclaw onboard --auth-choice zai-api-key.

  • General endpoint: https://api.z.ai/api/paas/v4
  • Coding endpoint (default): https://api.z.ai/api/coding/paas/v4
  • For the general endpoint, define a custom provider with the base URL override.
{
  env: { MOONSHOT_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "moonshot/kimi-k2.6" },
      models: { "moonshot/kimi-k2.6": { alias: "Kimi K2.6" } },
    },
  },
  models: {
    mode: "merge",
    providers: {
      moonshot: {
        baseUrl: "https://api.moonshot.ai/v1",
        apiKey: "${MOONSHOT_API_KEY}",
        api: "openai-completions",
        models: [
          {
            id: "kimi-k2.6",
            name: "Kimi K2.6",
            reasoning: false,
            input: ["text", "image"],
            cost: { input: 0.95, output: 4, cacheRead: 0.16, cacheWrite: 0 },
            contextWindow: 262144,
            maxTokens: 262144,
          },
        ],
      },
    },
  },
}

For the China endpoint: baseUrl: "https://api.moonshot.cn/v1" or openclaw onboard --auth-choice moonshot-api-key-cn.

Native Moonshot endpoints advertise streaming usage compatibility on the shared openai-completions transport, and OpenClaw keys that off endpoint capabilities rather than the built-in provider id alone.

{
  env: { KIMI_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "kimi/kimi-code" },
      models: { "kimi/kimi-code": { alias: "Kimi Code" } },
    },
  },
}

Anthropic-compatible, built-in provider. Shortcut: openclaw onboard --auth-choice kimi-code-api-key.

{
  env: { SYNTHETIC_API_KEY: "sk-..." },
  agents: {
    defaults: {
      model: { primary: "synthetic/hf:MiniMaxAI/MiniMax-M2.5" },
      models: { "synthetic/hf:MiniMaxAI/MiniMax-M2.5": { alias: "MiniMax M2.5" } },
    },
  },
  models: {
    mode: "merge",
    providers: {
      synthetic: {
        baseUrl: "https://api.synthetic.new/anthropic",
        apiKey: "${SYNTHETIC_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "hf:MiniMaxAI/MiniMax-M2.5",
            name: "MiniMax M2.5",
            reasoning: true,
            input: ["text"],
            cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
            contextWindow: 192000,
            maxTokens: 65536,
          },
        ],
      },
    },
  },
}

Base URL should omit /v1 (Anthropic client appends it). Shortcut: openclaw onboard --auth-choice synthetic-api-key.

{
  agents: {
    defaults: {
      model: { primary: "minimax/MiniMax-M2.7" },
      models: {
        "minimax/MiniMax-M2.7": { alias: "Minimax" },
      },
    },
  },
  models: {
    mode: "merge",
    providers: {
      minimax: {
        baseUrl: "https://api.minimax.io/anthropic",
        apiKey: "${MINIMAX_API_KEY}",
        api: "anthropic-messages",
        models: [
          {
            id: "MiniMax-M2.7",
            name: "MiniMax M2.7",
            reasoning: true,
            input: ["text", "image"],
            cost: { input: 0.3, output: 1.2, cacheRead: 0.06, cacheWrite: 0.375 },
            contextWindow: 204800,
            maxTokens: 131072,
          },
        ],
      },
    },
  },
}

Set MINIMAX_API_KEY. Shortcuts: openclaw onboard --auth-choice minimax-global-api or openclaw onboard --auth-choice minimax-cn-api. The model catalog defaults to M2.7 only. On the Anthropic-compatible streaming path, OpenClaw disables MiniMax thinking by default unless you explicitly set thinking yourself. /fast on or params.fastMode: true rewrites MiniMax-M2.7 to MiniMax-M2.7-highspeed.

See Local Models. TL;DR: run a large local model via LM Studio Responses API on serious hardware; keep hosted models merged for fallback.


Skills

{
  skills: {
    allowBundled: ["gemini", "peekaboo"],
    load: {
      extraDirs: ["~/Projects/agent-scripts/skills"],
    },
    install: {
      preferBrew: true,
      nodeManager: "npm", // npm | pnpm | yarn | bun
    },
    entries: {
      "image-lab": {
        apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string
        env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
      },
      peekaboo: { enabled: true },
      sag: { enabled: false },
    },
  },
}
  • allowBundled: optional allowlist for bundled skills only (managed/workspace skills unaffected).
  • load.extraDirs: extra shared skill roots (lowest precedence).
  • install.preferBrew: when true, prefer Homebrew installers when brew is available before falling back to other installer kinds.
  • install.nodeManager: node installer preference for metadata.openclaw.install specs (npm | pnpm | yarn | bun).
  • entries.<skillKey>.enabled: false disables a skill even if bundled/installed.
  • entries.<skillKey>.apiKey: convenience for skills declaring a primary env var (plaintext string or SecretRef object).

Plugins

{
  plugins: {
    enabled: true,
    allow: ["voice-call"],
    deny: [],
    load: {
      paths: ["~/Projects/oss/voice-call-plugin"],
    },
    entries: {
      "voice-call": {
        enabled: true,
        hooks: {
          allowPromptInjection: false,
        },
        config: { provider: "twilio" },
      },
    },
  },
}
  • Loaded from ~/.openclaw/extensions, <workspace>/.openclaw/extensions, plus plugins.load.paths.
  • Discovery accepts native OpenClaw plugins plus compatible Codex bundles and Claude bundles, including manifestless Claude default-layout bundles.
  • Config changes require a gateway restart.
  • allow: optional allowlist (only listed plugins load). deny wins.
  • plugins.entries.<id>.apiKey: plugin-level API key convenience field (when supported by the plugin).
  • plugins.entries.<id>.env: plugin-scoped env var map.
  • plugins.entries.<id>.hooks.allowPromptInjection: when false, core blocks before_prompt_build and ignores prompt-mutating fields from legacy before_agent_start, while preserving legacy modelOverride and providerOverride. Applies to native plugin hooks and supported bundle-provided hook directories.
  • plugins.entries.<id>.subagent.allowModelOverride: explicitly trust this plugin to request per-run provider and model overrides for background subagent runs.
  • plugins.entries.<id>.subagent.allowedModels: optional allowlist of canonical provider/model targets for trusted subagent overrides. Use "*" only when you intentionally want to allow any model.
  • plugins.entries.<id>.config: plugin-defined config object (validated by native OpenClaw plugin schema when available).
  • plugins.entries.firecrawl.config.webFetch: Firecrawl web-fetch provider settings.
    • apiKey: Firecrawl API key (accepts SecretRef). Falls back to plugins.entries.firecrawl.config.webSearch.apiKey, legacy tools.web.fetch.firecrawl.apiKey, or FIRECRAWL_API_KEY env var.
    • baseUrl: Firecrawl API base URL (default: https://api.firecrawl.dev).
    • onlyMainContent: extract only the main content from pages (default: true).
    • maxAgeMs: maximum cache age in milliseconds (default: 172800000 / 2 days).
    • timeoutSeconds: scrape request timeout in seconds (default: 60).
  • plugins.entries.xai.config.xSearch: xAI X Search (Grok web search) settings.
    • enabled: enable the X Search provider.
    • model: Grok model to use for search (e.g. "grok-4-1-fast").
  • plugins.entries.memory-core.config.dreaming: memory dreaming settings. See Dreaming for phases and thresholds.
    • enabled: master dreaming switch (default false).
    • frequency: cron cadence for each full dreaming sweep ("0 3 * * *" by default).
    • phase policy and thresholds are implementation details (not user-facing config keys).
  • Full memory config lives in Memory configuration reference:
    • agents.defaults.memorySearch.*
    • memory.backend
    • memory.citations
    • memory.qmd.*
    • plugins.entries.memory-core.config.dreaming
  • Enabled Claude bundle plugins can also contribute embedded Pi defaults from settings.json; OpenClaw applies those as sanitized agent settings, not as raw OpenClaw config patches.
  • plugins.slots.memory: pick the active memory plugin id, or "none" to disable memory plugins.
  • plugins.slots.contextEngine: pick the active context engine plugin id; defaults to "legacy" unless you install and select another engine.
  • plugins.installs: CLI-managed install metadata used by openclaw plugins update.
    • Includes source, spec, sourcePath, installPath, version, resolvedName, resolvedVersion, resolvedSpec, integrity, shasum, resolvedAt, installedAt.
    • Treat plugins.installs.* as managed state; prefer CLI commands over manual edits.

See Plugins.


Browser

{
  browser: {
    enabled: true,
    evaluateEnabled: true,
    defaultProfile: "user",
    ssrfPolicy: {
      // dangerouslyAllowPrivateNetwork: true, // opt in only for trusted private-network access
      // allowPrivateNetwork: true, // legacy alias
      // hostnameAllowlist: ["*.example.com", "example.com"],
      // allowedHostnames: ["localhost"],
    },
    profiles: {
      openclaw: { cdpPort: 18800, color: "#FF4500" },
      work: { cdpPort: 18801, color: "#0066CC" },
      user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
      brave: {
        driver: "existing-session",
        attachOnly: true,
        userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
        color: "#FB542B",
      },
      remote: { cdpUrl: "http://10.0.0.42:9222", color: "#00AA00" },
    },
    color: "#FF4500",
    // headless: false,
    // noSandbox: false,
    // extraArgs: [],
    // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
    // attachOnly: false,
  },
}
  • evaluateEnabled: false disables act:evaluate and wait --fn.
  • ssrfPolicy.dangerouslyAllowPrivateNetwork is disabled when unset, so browser navigation stays strict by default.
  • Set ssrfPolicy.dangerouslyAllowPrivateNetwork: true only when you intentionally trust private-network browser navigation.
  • In strict mode, remote CDP profile endpoints (profiles.*.cdpUrl) are subject to the same private-network blocking during reachability/discovery checks.
  • ssrfPolicy.allowPrivateNetwork remains supported as a legacy alias.
  • In strict mode, use ssrfPolicy.hostnameAllowlist and ssrfPolicy.allowedHostnames for explicit exceptions.
  • Remote profiles are attach-only (start/stop/reset disabled).
  • profiles.*.cdpUrl accepts http://, https://, ws://, and wss://. Use HTTP(S) when you want OpenClaw to discover /json/version; use WS(S) when your provider gives you a direct DevTools WebSocket URL.
  • existing-session profiles use Chrome MCP instead of CDP and can attach on the selected host or through a connected browser node.
  • existing-session profiles can set userDataDir to target a specific Chromium-based browser profile such as Brave or Edge.
  • existing-session profiles keep the current Chrome MCP route limits: snapshot/ref-driven actions instead of CSS-selector targeting, one-file upload hooks, no dialog timeout overrides, no wait --load networkidle, and no responsebody, PDF export, download interception, or batch actions.
  • Local managed openclaw profiles auto-assign cdpPort and cdpUrl; only set cdpUrl explicitly for remote CDP.
  • Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary.
  • Control service: loopback only (port derived from gateway.port, default 18791).
  • extraArgs appends extra launch flags to local Chromium startup (for example --disable-gpu, window sizing, or debug flags).

UI

{
  ui: {
    seamColor: "#FF4500",
    assistant: {
      name: "OpenClaw",
      avatar: "CB", // emoji, short text, image URL, or data URI
    },
  },
}
  • seamColor: accent color for native app UI chrome (Talk Mode bubble tint, etc.).
  • assistant: Control UI identity override. Falls back to active agent identity.

Gateway

{
  gateway: {
    mode: "local", // local | remote
    port: 18789,
    bind: "loopback",
    auth: {
      mode: "token", // none | token | password | trusted-proxy
      token: "your-token",
      // password: "your-password", // or OPENCLAW_GATEWAY_PASSWORD
      // trustedProxy: { userHeader: "x-forwarded-user" }, // for mode=trusted-proxy; see /gateway/trusted-proxy-auth
      allowTailscale: true,
      rateLimit: {
        maxAttempts: 10,
        windowMs: 60000,
        lockoutMs: 300000,
        exemptLoopback: true,
      },
    },
    tailscale: {
      mode: "off", // off | serve | funnel
      resetOnExit: false,
    },
    controlUi: {
      enabled: true,
      basePath: "/openclaw",
      // root: "dist/control-ui",
      // embedSandbox: "scripts", // strict | scripts | trusted
      // allowExternalEmbedUrls: false, // dangerous: allow absolute external http(s) embed URLs
      // allowedOrigins: ["https://control.example.com"], // required for non-loopback Control UI
      // dangerouslyAllowHostHeaderOriginFallback: false, // dangerous Host-header origin fallback mode
      // allowInsecureAuth: false,
      // dangerouslyDisableDeviceAuth: false,
    },
    remote: {
      url: "ws://gateway.tailnet:18789",
      transport: "ssh", // ssh | direct
      token: "your-token",
      // password: "your-password",
    },
    trustedProxies: ["10.0.0.1"],
    // Optional. Default false.
    allowRealIpFallback: false,
    tools: {
      // Additional /tools/invoke HTTP denies
      deny: ["browser"],
      // Remove tools from the default HTTP deny list
      allow: ["gateway"],
    },
    push: {
      apns: {
        relay: {
          baseUrl: "https://relay.example.com",
          timeoutMs: 10000,
        },
      },
    },
  },
}
  • mode: local (run gateway) or remote (connect to remote gateway). Gateway refuses to start unless local.
  • port: single multiplexed port for WS + HTTP. Precedence: --port > OPENCLAW_GATEWAY_PORT > gateway.port > 18789.
  • bind: auto, loopback (default), lan (0.0.0.0), tailnet (Tailscale IP only), or custom.
  • Legacy bind aliases: use bind mode values in gateway.bind (auto, loopback, lan, tailnet, custom), not host aliases (0.0.0.0, 127.0.0.1, localhost, ::, ::1).
  • Docker note: the default loopback bind listens on 127.0.0.1 inside the container. With Docker bridge networking (-p 18789:18789), traffic arrives on eth0, so the gateway is unreachable. Use --network host, or set bind: "lan" (or bind: "custom" with customBindHost: "0.0.0.0") to listen on all interfaces.
  • Auth: required by default. Non-loopback binds require gateway auth. In practice that means a shared token/password or an identity-aware reverse proxy with gateway.auth.mode: "trusted-proxy". Onboarding wizard generates a token by default.
  • If both gateway.auth.token and gateway.auth.password are configured (including SecretRefs), set gateway.auth.mode explicitly to token or password. Startup and service install/repair flows fail when both are configured and mode is unset.
  • gateway.auth.mode: "none": explicit no-auth mode. Use only for trusted local loopback setups; this is intentionally not offered by onboarding prompts.
  • gateway.auth.mode: "trusted-proxy": delegate auth to an identity-aware reverse proxy and trust identity headers from gateway.trustedProxies (see Trusted Proxy Auth). This mode expects a non-loopback proxy source; same-host loopback reverse proxies do not satisfy trusted-proxy auth.
  • gateway.auth.allowTailscale: when true, Tailscale Serve identity headers can satisfy Control UI/WebSocket auth (verified via tailscale whois). HTTP API endpoints do not use that Tailscale header auth; they follow the gateway's normal HTTP auth mode instead. This tokenless flow assumes the gateway host is trusted. Defaults to true when tailscale.mode = "serve".
  • gateway.auth.rateLimit: optional failed-auth limiter. Applies per client IP and per auth scope (shared-secret and device-token are tracked independently). Blocked attempts return 429 + Retry-After.
    • On the async Tailscale Serve Control UI path, failed attempts for the same {scope, clientIp} are serialized before the failure write. Concurrent bad attempts from the same client can therefore trip the limiter on the second request instead of both racing through as plain mismatches.
    • gateway.auth.rateLimit.exemptLoopback defaults to true; set false when you intentionally want localhost traffic rate-limited too (for test setups or strict proxy deployments).
  • Browser-origin WS auth attempts are always throttled with loopback exemption disabled (defense-in-depth against browser-based localhost brute force).
  • On loopback, those browser-origin lockouts are isolated per normalized Origin value, so repeated failures from one localhost origin do not automatically lock out a different origin.
  • tailscale.mode: serve (tailnet only, loopback bind) or funnel (public, requires auth).
  • controlUi.allowedOrigins: explicit browser-origin allowlist for Gateway WebSocket connects. Required when browser clients are expected from non-loopback origins.
  • controlUi.dangerouslyAllowHostHeaderOriginFallback: dangerous mode that enables Host-header origin fallback for deployments that intentionally rely on Host-header origin policy.
  • remote.transport: ssh (default) or direct (ws/wss). For direct, remote.url must be ws:// or wss://.
  • OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1: client-side break-glass override that allows plaintext ws:// to trusted private-network IPs; default remains loopback-only for plaintext.
  • gateway.remote.token / .password are remote-client credential fields. They do not configure gateway auth by themselves.
  • gateway.push.apns.relay.baseUrl: base HTTPS URL for the external APNs relay used by official/TestFlight iOS builds after they publish relay-backed registrations to the gateway. This URL must match the relay URL compiled into the iOS build.
  • gateway.push.apns.relay.timeoutMs: gateway-to-relay send timeout in milliseconds. Defaults to 10000.
  • Relay-backed registrations are delegated to a specific gateway identity. The paired iOS app fetches gateway.identity.get, includes that identity in the relay registration, and forwards a registration-scoped send grant to the gateway. Another gateway cannot reuse that stored registration.
  • OPENCLAW_APNS_RELAY_BASE_URL / OPENCLAW_APNS_RELAY_TIMEOUT_MS: temporary env overrides for the relay config above.
  • OPENCLAW_APNS_RELAY_ALLOW_HTTP=true: development-only escape hatch for loopback HTTP relay URLs. Production relay URLs should stay on HTTPS.
  • gateway.channelHealthCheckMinutes: channel health-monitor interval in minutes. Set 0 to disable health-monitor restarts globally. Default: 5.
  • gateway.channelStaleEventThresholdMinutes: stale-socket threshold in minutes. Keep this greater than or equal to gateway.channelHealthCheckMinutes. Default: 30.
  • gateway.channelMaxRestartsPerHour: maximum health-monitor restarts per channel/account in a rolling hour. Default: 10.
  • channels.<provider>.healthMonitor.enabled: per-channel opt-out for health-monitor restarts while keeping the global monitor enabled.
  • channels.<provider>.accounts.<accountId>.healthMonitor.enabled: per-account override for multi-account channels. When set, it takes precedence over the channel-level override.
  • Local gateway call paths can use gateway.remote.* as fallback only when gateway.auth.* is unset.
  • If gateway.auth.token / gateway.auth.password is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).
  • trustedProxies: reverse proxy IPs that terminate TLS or inject forwarded-client headers. Only list proxies you control. Loopback entries are still valid for same-host proxy/local-detection setups (for example Tailscale Serve or a local reverse proxy), but they do not make loopback requests eligible for gateway.auth.mode: "trusted-proxy".
  • allowRealIpFallback: when true, the gateway accepts X-Real-IP if X-Forwarded-For is missing. Default false for fail-closed behavior.
  • gateway.tools.deny: extra tool names blocked for HTTP POST /tools/invoke (extends default deny list).
  • gateway.tools.allow: remove tool names from the default HTTP deny list.

OpenAI-compatible endpoints

  • Chat Completions: disabled by default. Enable with gateway.http.endpoints.chatCompletions.enabled: true.
  • Responses API: gateway.http.endpoints.responses.enabled.
  • Responses URL-input hardening:
    • gateway.http.endpoints.responses.maxUrlParts
    • gateway.http.endpoints.responses.files.urlAllowlist
    • gateway.http.endpoints.responses.images.urlAllowlist Empty allowlists are treated as unset; use gateway.http.endpoints.responses.files.allowUrl=false and/or gateway.http.endpoints.responses.images.allowUrl=false to disable URL fetching.
  • Optional response hardening header:
    • gateway.http.securityHeaders.strictTransportSecurity (set only for HTTPS origins you control; see Trusted Proxy Auth)

Multi-instance isolation

Run multiple gateways on one host with unique ports and state dirs:

OPENCLAW_CONFIG_PATH=~/.openclaw/a.json \
OPENCLAW_STATE_DIR=~/.openclaw-a \
openclaw gateway --port 19001

Convenience flags: --dev (uses ~/.openclaw-dev + port 19001), --profile <name> (uses ~/.openclaw-<name>).

See Multiple Gateways.

gateway.tls

{
  gateway: {
    tls: {
      enabled: false,
      autoGenerate: false,
      certPath: "/etc/openclaw/tls/server.crt",
      keyPath: "/etc/openclaw/tls/server.key",
      caPath: "/etc/openclaw/tls/ca-bundle.crt",
    },
  },
}
  • enabled: enables TLS termination at the gateway listener (HTTPS/WSS) (default: false).
  • autoGenerate: auto-generates a local self-signed cert/key pair when explicit files are not configured; for local/dev use only.
  • certPath: filesystem path to the TLS certificate file.
  • keyPath: filesystem path to the TLS private key file; keep permission-restricted.
  • caPath: optional CA bundle path for client verification or custom trust chains.

gateway.reload

{
  gateway: {
    reload: {
      mode: "hybrid", // off | restart | hot | hybrid
      debounceMs: 500,
      deferralTimeoutMs: 300000,
    },
  },
}
  • mode: controls how config edits are applied at runtime.
    • "off": ignore live edits; changes require an explicit restart.
    • "restart": always restart the gateway process on config change.
    • "hot": apply changes in-process without restarting.
    • "hybrid" (default): try hot reload first; fall back to restart if required.
  • debounceMs: debounce window in ms before config changes are applied (non-negative integer).
  • deferralTimeoutMs: maximum time in ms to wait for in-flight operations before forcing a restart (default: 300000 = 5 minutes).

Hooks

{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
    maxBodyBytes: 262144,
    defaultSessionKey: "hook:ingress",
    allowRequestSessionKey: true,
    allowedSessionKeyPrefixes: ["hook:", "hook:gmail:"],
    allowedAgentIds: ["hooks", "main"],
    presets: ["gmail"],
    transformsDir: "~/.openclaw/hooks/transforms",
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        agentId: "hooks",
        wakeMode: "now",
        name: "Gmail",
        sessionKey: "hook:gmail:{{messages[0].id}}",
        messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
        deliver: true,
        channel: "last",
        model: "openai/gpt-5.4-mini",
      },
    ],
  },
}

Auth: Authorization: Bearer <token> or x-openclaw-token: <token>. Query-string hook tokens are rejected.

Validation and safety notes:

  • hooks.enabled=true requires a non-empty hooks.token.
  • hooks.token must be distinct from gateway.auth.token; reusing the Gateway token is rejected.
  • hooks.path cannot be /; use a dedicated subpath such as /hooks.
  • If hooks.allowRequestSessionKey=true, constrain hooks.allowedSessionKeyPrefixes (for example ["hook:"]).
  • If a mapping or preset uses a templated sessionKey, set hooks.allowedSessionKeyPrefixes and hooks.allowRequestSessionKey=true. Static mapping keys do not require that opt-in.

Endpoints:

  • POST /hooks/wake{ text, mode?: "now"|"next-heartbeat" }
  • POST /hooks/agent{ message, name?, agentId?, sessionKey?, wakeMode?, deliver?, channel?, to?, model?, thinking?, timeoutSeconds? }
    • sessionKey from request payload is accepted only when hooks.allowRequestSessionKey=true (default: false).
  • POST /hooks/<name> → resolved via hooks.mappings
    • Template-rendered mapping sessionKey values are treated as externally supplied and also require hooks.allowRequestSessionKey=true.
  • match.path matches sub-path after /hooks (e.g. /hooks/gmailgmail).
  • match.source matches a payload field for generic paths.
  • Templates like {{messages[0].subject}} read from the payload.
  • transform can point to a JS/TS module returning a hook action.
    • transform.module must be a relative path and stays within hooks.transformsDir (absolute paths and traversal are rejected).
  • agentId routes to a specific agent; unknown IDs fall back to default.
  • allowedAgentIds: restricts explicit routing (* or omitted = allow all, [] = deny all).
  • defaultSessionKey: optional fixed session key for hook agent runs without explicit sessionKey.
  • allowRequestSessionKey: allow /hooks/agent callers and template-driven mapping session keys to set sessionKey (default: false).
  • allowedSessionKeyPrefixes: optional prefix allowlist for explicit sessionKey values (request + mapping), e.g. ["hook:"]. It becomes required when any mapping or preset uses a templated sessionKey.
  • deliver: true sends final reply to a channel; channel defaults to last.
  • model overrides LLM for this hook run (must be allowed if model catalog is set).

Gmail integration

  • The built-in Gmail preset uses sessionKey: "hook:gmail:{{messages[0].id}}".
  • If you keep that per-message routing, set hooks.allowRequestSessionKey: true and constrain hooks.allowedSessionKeyPrefixes to match the Gmail namespace, for example ["hook:", "hook:gmail:"].
  • If you need hooks.allowRequestSessionKey: false, override the preset with a static sessionKey instead of the templated default.
{
  hooks: {
    gmail: {
      account: "openclaw@gmail.com",
      topic: "projects/<project-id>/topics/gog-gmail-watch",
      subscription: "gog-gmail-watch-push",
      pushToken: "shared-push-token",
      hookUrl: "http://127.0.0.1:18789/hooks/gmail",
      includeBody: true,
      maxBytes: 20000,
      renewEveryMinutes: 720,
      serve: { bind: "127.0.0.1", port: 8788, path: "/" },
      tailscale: { mode: "funnel", path: "/gmail-pubsub" },
      model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
      thinking: "off",
    },
  },
}
  • Gateway auto-starts gog gmail watch serve on boot when configured. Set OPENCLAW_SKIP_GMAIL_WATCHER=1 to disable.
  • Don't run a separate gog gmail watch serve alongside the Gateway.

Canvas host

{
  canvasHost: {
    root: "~/.openclaw/workspace/canvas",
    liveReload: true,
    // enabled: false, // or OPENCLAW_SKIP_CANVAS_HOST=1
  },
}
  • Serves agent-editable HTML/CSS/JS and A2UI over HTTP under the Gateway port:
    • http://<gateway-host>:<gateway.port>/__openclaw__/canvas/
    • http://<gateway-host>:<gateway.port>/__openclaw__/a2ui/
  • Local-only: keep gateway.bind: "loopback" (default).
  • Non-loopback binds: canvas routes require Gateway auth (token/password/trusted-proxy), same as other Gateway HTTP surfaces.
  • Node WebViews typically don't send auth headers; after a node is paired and connected, the Gateway advertises node-scoped capability URLs for canvas/A2UI access.
  • Capability URLs are bound to the active node WS session and expire quickly. IP-based fallback is not used.
  • Injects live-reload client into served HTML.
  • Auto-creates starter index.html when empty.
  • Also serves A2UI at /__openclaw__/a2ui/.
  • Changes require a gateway restart.
  • Disable live reload for large directories or EMFILE errors.

Discovery

mDNS (Bonjour)

{
  discovery: {
    mdns: {
      mode: "minimal", // minimal | full | off
    },
  },
}
  • minimal (default): omit cliPath + sshPort from TXT records.
  • full: include cliPath + sshPort.
  • Hostname defaults to openclaw. Override with OPENCLAW_MDNS_HOSTNAME.

Wide-area (DNS-SD)

{
  discovery: {
    wideArea: { enabled: true },
  },
}

Writes a unicast DNS-SD zone under ~/.openclaw/dns/. For cross-network discovery, pair with a DNS server (CoreDNS recommended) + Tailscale split DNS.

Setup: openclaw dns setup --apply.


Environment

env (inline env vars)

{
  env: {
    OPENROUTER_API_KEY: "sk-or-...",
    vars: {
      GROQ_API_KEY: "gsk-...",
    },
    shellEnv: {
      enabled: true,
      timeoutMs: 15000,
    },
  },
}
  • Inline env vars are only applied if the process env is missing the key.
  • .env files: CWD .env + ~/.openclaw/.env (neither overrides existing vars).
  • shellEnv: imports missing expected keys from your login shell profile.
  • See Environment for full precedence.

Env var substitution

Reference env vars in any config string with ${VAR_NAME}:

{
  gateway: {
    auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" },
  },
}
  • Only uppercase names matched: [A-Z_][A-Z0-9_]*.
  • Missing/empty vars throw an error at config load.
  • Escape with $${VAR} for a literal ${VAR}.
  • Works with $include.

Secrets

Secret refs are additive: plaintext values still work.

SecretRef

Use one object shape:

{ source: "env" | "file" | "exec", provider: "default", id: "..." }

Validation:

  • provider pattern: ^[a-z][a-z0-9_-]{0,63}$
  • source: "env" id pattern: ^[A-Z][A-Z0-9_]{0,127}$
  • source: "file" id: absolute JSON pointer (for example "/providers/openai/apiKey")
  • source: "exec" id pattern: ^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$
  • source: "exec" ids must not contain . or .. slash-delimited path segments (for example a/../b is rejected)

Supported credential surface

  • Canonical matrix: SecretRef Credential Surface
  • secrets apply targets supported openclaw.json credential paths.
  • auth-profiles.json refs are included in runtime resolution and audit coverage.

Secret providers config

{
  secrets: {
    providers: {
      default: { source: "env" }, // optional explicit env provider
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json",
        timeoutMs: 5000,
      },
      vault: {
        source: "exec",
        command: "/usr/local/bin/openclaw-vault-resolver",
        passEnv: ["PATH", "VAULT_ADDR"],
      },
    },
    defaults: {
      env: "default",
      file: "filemain",
      exec: "vault",
    },
  },
}

Notes:

  • file provider supports mode: "json" and mode: "singleValue" (id must be "value" in singleValue mode).
  • File and exec provider paths fail closed when Windows ACL verification is unavailable. Set allowInsecurePath: true only for trusted paths that cannot be verified.
  • exec provider requires an absolute command path and uses protocol payloads on stdin/stdout.
  • By default, symlink command paths are rejected. Set allowSymlinkCommand: true to allow symlink paths while validating the resolved target path.
  • If trustedDirs is configured, the trusted-dir check applies to the resolved target path.
  • exec child environment is minimal by default; pass required variables explicitly with passEnv.
  • Secret refs are resolved at activation time into an in-memory snapshot, then request paths read the snapshot only.
  • Active-surface filtering applies during activation: unresolved refs on enabled surfaces fail startup/reload, while inactive surfaces are skipped with diagnostics.

Auth storage

{
  auth: {
    profiles: {
      "anthropic:default": { provider: "anthropic", mode: "api_key" },
      "anthropic:work": { provider: "anthropic", mode: "api_key" },
      "openai-codex:personal": { provider: "openai-codex", mode: "oauth" },
    },
    order: {
      anthropic: ["anthropic:default", "anthropic:work"],
      "openai-codex": ["openai-codex:personal"],
    },
  },
}
  • Per-agent profiles are stored at <agentDir>/auth-profiles.json.
  • auth-profiles.json supports value-level refs (keyRef for api_key, tokenRef for token) for static credential modes.
  • OAuth-mode profiles (auth.profiles.<id>.mode = "oauth") do not support SecretRef-backed auth-profile credentials.
  • Static runtime credentials come from in-memory resolved snapshots; legacy static auth.json entries are scrubbed when discovered.
  • Legacy OAuth imports from ~/.openclaw/credentials/oauth.json.
  • See OAuth.
  • Secrets runtime behavior and audit/configure/apply tooling: Secrets Management.

auth.cooldowns

{
  auth: {
    cooldowns: {
      billingBackoffHours: 5,
      billingBackoffHoursByProvider: { anthropic: 3, openai: 8 },
      billingMaxHours: 24,
      authPermanentBackoffMinutes: 10,
      authPermanentMaxMinutes: 60,
      failureWindowHours: 24,
      overloadedProfileRotations: 1,
      overloadedBackoffMs: 0,
      rateLimitedProfileRotations: 1,
    },
  },
}
  • billingBackoffHours: base backoff in hours when a profile fails due to true billing/insufficient-credit errors (default: 5). Explicit billing text can still land here even on 401/403 responses, but provider-specific text matchers stay scoped to the provider that owns them (for example OpenRouter Key limit exceeded). Retryable HTTP 402 usage-window or organization/workspace spend-limit messages stay in the rate_limit path instead.
  • billingBackoffHoursByProvider: optional per-provider overrides for billing backoff hours.
  • billingMaxHours: cap in hours for billing backoff exponential growth (default: 24).
  • authPermanentBackoffMinutes: base backoff in minutes for high-confidence auth_permanent failures (default: 10).
  • authPermanentMaxMinutes: cap in minutes for auth_permanent backoff growth (default: 60).
  • failureWindowHours: rolling window in hours used for backoff counters (default: 24).
  • overloadedProfileRotations: maximum same-provider auth-profile rotations for overloaded errors before switching to model fallback (default: 1). Provider-busy shapes such as ModelNotReadyException land here.
  • overloadedBackoffMs: fixed delay before retrying an overloaded provider/profile rotation (default: 0).
  • rateLimitedProfileRotations: maximum same-provider auth-profile rotations for rate-limit errors before switching to model fallback (default: 1). That rate-limit bucket includes provider-shaped text such as Too many concurrent requests, ThrottlingException, concurrency limit reached, workers_ai ... quota limit exceeded, and resource exhausted.

Logging

{
  logging: {
    level: "info",
    file: "/tmp/openclaw/openclaw.log",
    consoleLevel: "info",
    consoleStyle: "pretty", // pretty | compact | json
    redactSensitive: "tools", // off | tools
    redactPatterns: ["\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1"],
  },
}
  • Default log file: /tmp/openclaw/openclaw-YYYY-MM-DD.log.
  • Set logging.file for a stable path.
  • consoleLevel bumps to debug when --verbose.
  • maxFileBytes: maximum log file size in bytes before writes are suppressed (positive integer; default: 524288000 = 500 MB). Use external log rotation for production deployments.

Diagnostics

{
  diagnostics: {
    enabled: true,
    flags: ["telegram.*"],
    stuckSessionWarnMs: 30000,

    otel: {
      enabled: false,
      endpoint: "https://otel-collector.example.com:4318",
      protocol: "http/protobuf", // http/protobuf | grpc
      headers: { "x-tenant-id": "my-org" },
      serviceName: "openclaw-gateway",
      traces: true,
      metrics: true,
      logs: false,
      sampleRate: 1.0,
      flushIntervalMs: 5000,
    },

    cacheTrace: {
      enabled: false,
      filePath: "~/.openclaw/logs/cache-trace.jsonl",
      includeMessages: true,
      includePrompt: true,
      includeSystem: true,
    },
  },
}
  • enabled: master toggle for instrumentation output (default: true).
  • flags: array of flag strings enabling targeted log output (supports wildcards like "telegram.*" or "*").
  • stuckSessionWarnMs: age threshold in ms for emitting stuck-session warnings while a session remains in processing state.
  • otel.enabled: enables the OpenTelemetry export pipeline (default: false).
  • otel.endpoint: collector URL for OTel export.
  • otel.protocol: "http/protobuf" (default) or "grpc".
  • otel.headers: extra HTTP/gRPC metadata headers sent with OTel export requests.
  • otel.serviceName: service name for resource attributes.
  • otel.traces / otel.metrics / otel.logs: enable trace, metrics, or log export.
  • otel.sampleRate: trace sampling rate 01.
  • otel.flushIntervalMs: periodic telemetry flush interval in ms.
  • cacheTrace.enabled: log cache trace snapshots for embedded runs (default: false).
  • cacheTrace.filePath: output path for cache trace JSONL (default: $OPENCLAW_STATE_DIR/logs/cache-trace.jsonl).
  • cacheTrace.includeMessages / includePrompt / includeSystem: control what is included in cache trace output (all default: true).

Update

{
  update: {
    channel: "stable", // stable | beta | dev
    checkOnStart: true,

    auto: {
      enabled: false,
      stableDelayHours: 6,
      stableJitterHours: 12,
      betaCheckIntervalHours: 1,
    },
  },
}
  • channel: release channel for npm/git installs — "stable", "beta", or "dev".
  • checkOnStart: check for npm updates when the gateway starts (default: true).
  • auto.enabled: enable background auto-update for package installs (default: false).
  • auto.stableDelayHours: minimum delay in hours before stable-channel auto-apply (default: 6; max: 168).
  • auto.stableJitterHours: extra stable-channel rollout spread window in hours (default: 12; max: 168).
  • auto.betaCheckIntervalHours: how often beta-channel checks run in hours (default: 1; max: 24).

ACP

{
  acp: {
    enabled: false,
    dispatch: { enabled: true },
    backend: "acpx",
    defaultAgent: "main",
    allowedAgents: ["main", "ops"],
    maxConcurrentSessions: 10,

    stream: {
      coalesceIdleMs: 50,
      maxChunkChars: 1000,
      repeatSuppression: true,
      deliveryMode: "live", // live | final_only
      hiddenBoundarySeparator: "paragraph", // none | space | newline | paragraph
      maxOutputChars: 50000,
      maxSessionUpdateChars: 500,
    },

    runtime: {
      ttlMinutes: 30,
    },
  },
}
  • enabled: global ACP feature gate (default: false).
  • dispatch.enabled: independent gate for ACP session turn dispatch (default: true). Set false to keep ACP commands available while blocking execution.
  • backend: default ACP runtime backend id (must match a registered ACP runtime plugin).
  • defaultAgent: fallback ACP target agent id when spawns do not specify an explicit target.
  • allowedAgents: allowlist of agent ids permitted for ACP runtime sessions; empty means no additional restriction.
  • maxConcurrentSessions: maximum concurrently active ACP sessions.
  • stream.coalesceIdleMs: idle flush window in ms for streamed text.
  • stream.maxChunkChars: maximum chunk size before splitting streamed block projection.
  • stream.repeatSuppression: suppress repeated status/tool lines per turn (default: true).
  • stream.deliveryMode: "live" streams incrementally; "final_only" buffers until turn terminal events.
  • stream.hiddenBoundarySeparator: separator before visible text after hidden tool events (default: "paragraph").
  • stream.maxOutputChars: maximum assistant output characters projected per ACP turn.
  • stream.maxSessionUpdateChars: maximum characters for projected ACP status/update lines.
  • stream.tagVisibility: record of tag names to boolean visibility overrides for streamed events.
  • runtime.ttlMinutes: idle TTL in minutes for ACP session workers before eligible cleanup.
  • runtime.installCommand: optional install command to run when bootstrapping an ACP runtime environment.

CLI

{
  cli: {
    banner: {
      taglineMode: "off", // random | default | off
    },
  },
}
  • cli.banner.taglineMode controls banner tagline style:
    • "random" (default): rotating funny/seasonal taglines.
    • "default": fixed neutral tagline (All your chats, one OpenClaw.).
    • "off": no tagline text (banner title/version still shown).
  • To hide the entire banner (not just taglines), set env OPENCLAW_HIDE_BANNER=1.

Wizard

Metadata written by CLI guided setup flows (onboard, configure, doctor):

{
  wizard: {
    lastRunAt: "2026-01-01T00:00:00.000Z",
    lastRunVersion: "2026.1.4",
    lastRunCommit: "abc1234",
    lastRunCommand: "configure",
    lastRunMode: "local",
  },
}

Identity

See agents.list identity fields under Agent defaults.


Bridge (legacy, removed)

Current builds no longer include the TCP bridge. Nodes connect over the Gateway WebSocket. bridge.* keys are no longer part of the config schema (validation fails until removed; openclaw doctor --fix can strip unknown keys).

{
  "bridge": {
    "enabled": true,
    "port": 18790,
    "bind": "tailnet",
    "tls": {
      "enabled": true,
      "autoGenerate": true
    }
  }
}

Cron

{
  cron: {
    enabled: true,
    maxConcurrentRuns: 2,
    webhook: "https://example.invalid/legacy", // deprecated fallback for stored notify:true jobs
    webhookToken: "replace-with-dedicated-token", // optional bearer token for outbound webhook auth
    sessionRetention: "24h", // duration string or false
    runLog: {
      maxBytes: "2mb", // default 2_000_000 bytes
      keepLines: 2000, // default 2000
    },
  },
}
  • sessionRetention: how long to keep completed isolated cron run sessions before pruning from sessions.json. Also controls cleanup of archived deleted cron transcripts. Default: 24h; set false to disable.
  • runLog.maxBytes: max size per run log file (cron/runs/<jobId>.jsonl) before pruning. Default: 2_000_000 bytes.
  • runLog.keepLines: newest lines retained when run-log pruning is triggered. Default: 2000.
  • webhookToken: bearer token used for cron webhook POST delivery (delivery.mode = "webhook"), if omitted no auth header is sent.
  • webhook: deprecated legacy fallback webhook URL (http/https) used only for stored jobs that still have notify: true.

cron.retry

{
  cron: {
    retry: {
      maxAttempts: 3,
      backoffMs: [30000, 60000, 300000],
      retryOn: ["rate_limit", "overloaded", "network", "timeout", "server_error"],
    },
  },
}
  • maxAttempts: maximum retries for one-shot jobs on transient errors (default: 3; range: 010).
  • backoffMs: array of backoff delays in ms for each retry attempt (default: [30000, 60000, 300000]; 110 entries).
  • retryOn: error types that trigger retries — "rate_limit", "overloaded", "network", "timeout", "server_error". Omit to retry all transient types.

Applies only to one-shot cron jobs. Recurring jobs use separate failure handling.

cron.failureAlert

{
  cron: {
    failureAlert: {
      enabled: false,
      after: 3,
      cooldownMs: 3600000,
      mode: "announce",
      accountId: "main",
    },
  },
}
  • enabled: enable failure alerts for cron jobs (default: false).
  • after: consecutive failures before an alert fires (positive integer, min: 1).
  • cooldownMs: minimum milliseconds between repeated alerts for the same job (non-negative integer).
  • mode: delivery mode — "announce" sends via a channel message; "webhook" posts to the configured webhook.
  • accountId: optional account or channel id to scope alert delivery.

cron.failureDestination

{
  cron: {
    failureDestination: {
      mode: "announce",
      channel: "last",
      to: "channel:C1234567890",
      accountId: "main",
    },
  },
}
  • Default destination for cron failure notifications across all jobs.
  • mode: "announce" or "webhook"; defaults to "announce" when enough target data exists.
  • channel: channel override for announce delivery. "last" reuses the last known delivery channel.
  • to: explicit announce target or webhook URL. Required for webhook mode.
  • accountId: optional account override for delivery.
  • Per-job delivery.failureDestination overrides this global default.
  • When neither global nor per-job failure destination is set, jobs that already deliver via announce fall back to that primary announce target on failure.
  • delivery.failureDestination is only supported for sessionTarget="isolated" jobs unless the job's primary delivery.mode is "webhook".

See Cron Jobs. Isolated cron executions are tracked as background tasks.


Media model template variables

Template placeholders expanded in tools.media.models[].args:

Variable Description
{{Body}} Full inbound message body
{{RawBody}} Raw body (no history/sender wrappers)
{{BodyStripped}} Body with group mentions stripped
{{From}} Sender identifier
{{To}} Destination identifier
{{MessageSid}} Channel message id
{{SessionId}} Current session UUID
{{IsNewSession}} "true" when new session created
{{MediaUrl}} Inbound media pseudo-URL
{{MediaPath}} Local media path
{{MediaType}} Media type (image/audio/document/…)
{{Transcript}} Audio transcript
{{Prompt}} Resolved media prompt for CLI entries
{{MaxChars}} Resolved max output chars for CLI entries
{{ChatType}} "direct" or "group"
{{GroupSubject}} Group subject (best effort)
{{GroupMembers}} Group members preview (best effort)
{{SenderName}} Sender display name (best effort)
{{SenderE164}} Sender phone number (best effort)
{{Provider}} Provider hint (whatsapp, telegram, discord, etc.)

Config includes ($include)

Split config into multiple files:

// ~/.openclaw/openclaw.json
{
  gateway: { port: 18789 },
  agents: { $include: "./agents.json5" },
  broadcast: {
    $include: ["./clients/mueller.json5", "./clients/schmidt.json5"],
  },
}

Merge behavior:

  • Single file: replaces the containing object.
  • Array of files: deep-merged in order (later overrides earlier).
  • Sibling keys: merged after includes (override included values).
  • Nested includes: up to 10 levels deep.
  • Paths: resolved relative to the including file, but must stay inside the top-level config directory (dirname of openclaw.json). Absolute/../ forms are allowed only when they still resolve inside that boundary.
  • OpenClaw-owned writes that change only one top-level section backed by a single-file include write through to that included file. For example, plugins install updates plugins: { $include: "./plugins.json5" } in plugins.json5 and leaves openclaw.json intact.
  • Root includes, include arrays, and includes with sibling overrides are read-only for OpenClaw-owned writes; those writes fail closed instead of flattening the config.
  • Errors: clear messages for missing files, parse errors, and circular includes.

Related: Configuration · Configuration Examples · Doctor