docs/gateway/configuration-reference.md
Core config reference for ~/.openclaw/openclaw.json. For a task-oriented overview, see Configuration.
Covers the main OpenClaw config surfaces and links out when a subsystem has its own deeper reference. Channel- and plugin-owned command catalogs and deep memory/QMD knobs live on their own pages rather than on this one.
Code truth:
openclaw config schema prints the live JSON Schema used for validation and Control UI, with bundled/plugin/channel metadata merged in when availableconfig.schema.lookup returns one path-scoped schema node for drill-down toolingpnpm config:docs:check / pnpm config:docs:gen validate the config-doc baseline hash against the current schema surfaceAgent lookup path: use the gateway tool action config.schema.lookup for
exact field-level docs and constraints before edits. Use
Configuration for task-oriented guidance and this page
for the broader field map, defaults, and links to subsystem references.
Dedicated deep references:
agents.defaults.memorySearch.*, memory.qmd.*, memory.citations, and dreaming config under plugins.entries.memory-core.config.dreamingConfig format is JSON5 (comments + trailing commas allowed). All fields are optional — OpenClaw uses safe defaults when omitted.
Per-channel config keys moved to a dedicated page — see
Configuration — channels for channels.*,
including Slack, Discord, Telegram, WhatsApp, Matrix, iMessage, and other
bundled channels (auth, access control, multi-account, mention gating).
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)
talk.speechLocale: optional BCP 47 locale id for Talk speech recognition on iOS/macOStalk.silenceTimeoutMs: when unset, Talk keeps the platform default pause window before sending the transcript (700 ms on macOS and Android, 900 ms on iOS)Tool policy, experimental toggles, provider-backed tool config, and custom provider / base-URL setup moved to a dedicated page — see Configuration — tools and custom providers.
Provider definitions, model allowlists, and custom provider setup live in
Configuration — tools and custom providers.
The models root also owns global model-catalog behavior.
{
models: {
// Optional. Default: true. Requires a Gateway restart when changed.
pricing: { enabled: false },
},
}
models.mode: provider catalog behavior (merge or replace).models.providers: custom provider map keyed by provider id.models.pricing.enabled: controls the background pricing bootstrap that
starts after sidecars and channels reach the Gateway ready path. When false,
the Gateway skips OpenRouter and LiteLLM pricing-catalog fetches; configured
models.providers.*.models[].cost values still work for local cost estimates.OpenClaw-managed MCP server definitions live under mcp.servers and are
consumed by embedded Pi and other runtime adapters. The openclaw mcp list,
show, set, and unset commands manage this block without connecting to the
target server during config edits.
{
mcp: {
// Optional. Default: 600000 ms (10 minutes). Set 0 to disable idle eviction.
sessionIdleTtlMs: 600000,
servers: {
docs: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-fetch"],
},
remote: {
url: "https://example.com/mcp",
transport: "streamable-http", // streamable-http | sse
headers: {
Authorization: "Bearer ${MCP_REMOTE_TOKEN}",
},
},
},
},
}
mcp.servers: named stdio or remote MCP server definitions for runtimes that
expose configured MCP tools.
Remote entries use transport: "streamable-http" or transport: "sse";
type: "http" is a CLI-native alias that openclaw mcp set and
openclaw doctor --fix normalize into the canonical transport field.mcp.sessionIdleTtlMs: idle TTL for session-scoped bundled MCP runtimes.
One-shot embedded runs request run-end cleanup; this TTL is the backstop for
long-lived sessions and future callers.mcp.* hot-apply by disposing cached session MCP runtimes.
The next tool discovery/use recreates them from the new config, so removed
mcp.servers entries are reaped immediately instead of waiting for idle TTL.See MCP and CLI backends for runtime behavior.
{
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: {
enabled: true,
allow: ["voice-call"],
bundledDiscovery: "allowlist",
deny: [],
load: {
paths: ["~/Projects/oss/voice-call-plugin"],
},
entries: {
"voice-call": {
enabled: true,
hooks: {
allowPromptInjection: false,
},
config: { provider: "twilio" },
},
},
},
}
~/.openclaw/extensions, <workspace>/.openclaw/extensions, plus plugins.load.paths.allow: optional allowlist (only listed plugins load). deny wins.bundledDiscovery: defaults to "allowlist" for new configs, so a non-empty
plugins.allow also gates bundled provider plugins, including web-search
runtime providers. Doctor writes "compat" for migrated legacy allowlist
configs to preserve existing bundled provider behavior until you opt in.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>.hooks.allowConversationAccess: when true, trusted non-bundled plugins may read raw conversation content from typed hooks such as llm_input, llm_output, before_agent_finalize, and agent_end.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).channels.<id> and should be described by the owning plugin's manifest channelConfigs metadata, not by a central OpenClaw option registry.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; self-hosted overrides must target private/internal endpoints).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).model: optional Dream Diary subagent model override. Requires plugins.entries.memory-core.subagent.allowModelOverride: true; pair with allowedModels to restrict targets. Model-unavailable errors retry once with the session default model; trust or allowlist failures do not fall back silently.agents.defaults.memorySearch.*memory.backendmemory.citationsmemory.qmd.*plugins.entries.memory-core.config.dreamingsettings.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.See Plugins.
commitments controls inferred follow-up memory: OpenClaw can detect check-ins from conversation turns and deliver them through heartbeat runs.
commitments.enabled: enable hidden LLM extraction, storage, and heartbeat delivery for inferred follow-up commitments. Default: false.commitments.maxPerDay: maximum inferred follow-up commitments delivered per agent session in a rolling day. Default: 3.See Inferred commitments.
{
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"],
},
tabCleanup: {
enabled: true,
idleMinutes: 120,
maxTabsPerSession: 8,
sweepMinutes: 5,
},
profiles: {
openclaw: { cdpPort: 18800, color: "#FF4500" },
work: {
cdpPort: 18801,
color: "#0066CC",
executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
},
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.tabCleanup reclaims tracked primary-agent tabs after idle time or when a
session exceeds its cap. Set idleMinutes: 0 or maxTabsPerSession: 0 to
disable those individual cleanup modes.ssrfPolicy.dangerouslyAllowPrivateNetwork is disabled when unset, so browser navigation stays strict by default.ssrfPolicy.dangerouslyAllowPrivateNetwork: true only when you intentionally trust private-network browser navigation.profiles.*.cdpUrl) are subject to the same private-network blocking during reachability/discovery checks.ssrfPolicy.allowPrivateNetwork remains supported as a legacy alias.ssrfPolicy.hostnameAllowlist and ssrfPolicy.allowedHostnames for explicit exceptions.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.remoteCdpTimeoutMs and remoteCdpHandshakeTimeoutMs apply to remote and
attachOnly CDP reachability plus tab-opening requests. Managed loopback
profiles keep local CDP defaults.attachOnly: true; otherwise OpenClaw treats the loopback port as a
local managed browser profile and may report local port ownership errors.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.openclaw profiles auto-assign cdpPort and cdpUrl; only
set cdpUrl explicitly for remote CDP.executablePath to override the global
browser.executablePath for that profile. Use this to run one profile in
Chrome and another in Brave.browser.localLaunchTimeoutMs for Chrome CDP HTTP
discovery after process start and browser.localCdpReadyTimeoutMs for
post-launch CDP websocket readiness. Raise them on slower hosts where Chrome
starts successfully but readiness checks race startup. Both values must be
positive integers up to 120000 ms; invalid config values are rejected.browser.executablePath and browser.profiles.<name>.executablePath both
accept ~ and ~/... for your OS home directory before Chromium launch.
Per-profile userDataDir on existing-session profiles is also tilde-expanded.gateway.port, default 18791).extraArgs appends extra launch flags to local Chromium startup (for example
--disable-gpu, window sizing, or debug flags).{
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: {
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
// chatMessageMaxWidth: "min(1280px, 82%)", // optional grouped chat message max-width
// 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,
nodes: {
pairing: {
// Optional. Default unset/disabled.
autoApproveCidrs: ["192.168.1.0/24", "fd00:1234:5678::/64"],
},
allowCommands: ["canvas.navigate"],
denyCommands: ["system.run"],
},
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.gateway.bind (auto, loopback, lan, tailnet, custom), not host aliases (0.0.0.0, 127.0.0.1, localhost, ::, ::1).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.gateway.auth.mode: "trusted-proxy". Onboarding wizard generates a token by default.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 browser/user 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 by default; same-host loopback reverse proxies require explicit gateway.auth.trustedProxy.allowLoopback = true. Internal same-host callers can use gateway.auth.password as a local direct fallback; gateway.auth.token remains mutually exclusive with trusted-proxy mode.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.
{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).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.chatMessageMaxWidth: optional max-width for grouped Control UI chat messages. Accepts constrained CSS width values such as 960px, 82%, min(1280px, 82%), and calc(100% - 2rem).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 process-environment
break-glass override that allows plaintext ws:// to trusted private-network
IPs; default remains loopback-only for plaintext. There is no openclaw.json
equivalent, and browser private-network config such as
browser.ssrfPolicy.dangerouslyAllowPrivateNetwork does not affect Gateway
WebSocket clients.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.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.handshakeTimeoutMs: pre-auth Gateway WebSocket handshake timeout in milliseconds. Default: 15000. OPENCLAW_HANDSHAKE_TIMEOUT_MS takes precedence when set. Increase this on loaded or low-powered hosts where local clients can connect while startup warmup is still settling.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.gateway.remote.* as fallback only when gateway.auth.* is unset.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.nodes.pairing.autoApproveCidrs: optional CIDR/IP allowlist for auto-approving first-time node device pairing with no requested scopes. It is disabled when unset. This does not auto-approve operator/browser/Control UI/WebChat pairing, and it does not auto-approve role, scope, metadata, or public-key upgrades.gateway.nodes.allowCommands / gateway.nodes.denyCommands: global allow/deny shaping for declared node commands after pairing and platform allowlist evaluation. Use allowCommands to opt into dangerous node commands such as camera.snap, camera.clip, and screen.record; denyCommands removes a command even if a platform default or explicit allow would otherwise include it. After a node changes its declared command list, reject and re-approve that device pairing so the gateway stores the updated command snapshot.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.gateway.http.endpoints.chatCompletions.enabled: true.gateway.http.endpoints.responses.enabled.gateway.http.endpoints.responses.maxUrlPartsgateway.http.endpoints.responses.files.urlAllowlistgateway.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.gateway.http.securityHeaders.strictTransportSecurity (set only for HTTPS origins you control; see Trusted Proxy Auth)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: optional maximum time in ms to wait for in-flight operations before forcing a restart. Omit it to use the default bounded wait (300000); set 0 to wait indefinitely and log periodic still-pending warnings.{
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.hooks.allowRequestSessionKey=true, constrain hooks.allowedSessionKeyPrefixes (for example ["hook:"]).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
sessionKey values are treated as externally supplied and also require hooks.allowRequestSessionKey=true.match.path matches sub-path after /hooks (e.g. /hooks/gmail → gmail).match.source matches a payload field for generic paths.{{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).hooks.transformsDir under ~/.openclaw/hooks/transforms; workspace skill directories are rejected. If openclaw doctor reports this path as invalid, move the transform module into the hooks transforms directory or remove hooks.transformsDir.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).sessionKey: "hook:gmail:{{messages[0].id}}".hooks.allowRequestSessionKey: true and constrain hooks.allowedSessionKeyPrefixes to match the Gmail namespace, for example ["hook:", "hook:gmail:"].hooks.allowRequestSessionKey: false, override the preset with a static sessionKey instead of the templated default.{
hooks: {
gmail: {
account: "[email protected]",
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",
},
},
}
gog gmail watch serve on boot when configured. Set OPENCLAW_SKIP_GMAIL_WATCHER=1 to disable.gog gmail watch serve alongside the Gateway.{
canvasHost: {
root: "~/.openclaw/workspace/canvas",
liveReload: true,
// enabled: false, // or OPENCLAW_SKIP_CANVAS_HOST=1
},
}
http://<gateway-host>:<gateway.port>/__openclaw__/canvas/http://<gateway-host>:<gateway.port>/__openclaw__/a2ui/gateway.bind: "loopback" (default).index.html when empty./__openclaw__/a2ui/.EMFILE errors.{
discovery: {
mdns: {
mode: "minimal", // minimal | full | off
},
},
}
minimal (default when the bundled bonjour plugin is enabled): omit cliPath + sshPort from TXT records.full: include cliPath + sshPort; LAN multicast advertising still requires the bundled bonjour plugin to be enabled.off: suppress LAN multicast advertising without changing plugin enablement.bonjour plugin auto-starts on macOS hosts and is opt-in on Linux, Windows, and containerized Gateway deployments.openclaw. Override with OPENCLAW_MDNS_HOSTNAME.{
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.
env (inline env vars){
env: {
OPENROUTER_API_KEY: "sk-or-...",
vars: {
GROQ_API_KEY: "gsk-...",
},
shellEnv: {
enabled: true,
timeoutMs: 15000,
},
},
}
.env files: CWD .env + ~/.openclaw/.env (neither overrides existing vars).shellEnv: imports missing expected keys from your login shell profile.Reference env vars in any config string with ${VAR_NAME}:
{
gateway: {
auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" },
},
}
[A-Z_][A-Z0-9_]*.$${VAR} for a literal ${VAR}.$include.Secret refs are additive: plaintext values still work.
SecretRefUse 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)secrets apply targets supported openclaw.json credential paths.auth-profiles.json refs are included in runtime resolution and audit coverage.{
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).allowInsecurePath: true only for trusted paths that cannot be verified.exec provider requires an absolute command path and uses protocol payloads on stdin/stdout.allowSymlinkCommand: true to allow symlink paths while validating the resolved target path.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.{
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"],
},
},
}
<agentDir>/auth-profiles.json.auth-profiles.json supports value-level refs (keyRef for api_key, tokenRef for token) for static credential modes.auth-profiles.json maps such as { "provider": { "apiKey": "..." } } are not a runtime format; openclaw doctor --fix rewrites them to canonical provider:default API-key profiles with a .legacy-flat.*.bak backup.auth.profiles.<id>.mode = "oauth") do not support SecretRef-backed auth-profile credentials.auth.json entries are scrubbed when discovered.~/.openclaw/credentials/oauth.json.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: {
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"],
},
}
/tmp/openclaw/openclaw-YYYY-MM-DD.log.logging.file for a stable path.consoleLevel bumps to debug when --verbose.maxFileBytes: maximum active log file size in bytes before rotation (positive integer; default: 104857600 = 100 MB). OpenClaw keeps up to five numbered archives beside the active file.redactSensitive / redactPatterns: best-effort masking for console output, file logs, OTLP log records, and persisted session transcript text. redactSensitive: "off" only disables this general log/transcript policy; UI/tool/diagnostic safety surfaces still redact secrets before emission.{
diagnostics: {
enabled: true,
flags: ["telegram.*"],
stuckSessionWarnMs: 30000,
otel: {
enabled: false,
endpoint: "https://otel-collector.example.com:4318",
tracesEndpoint: "https://traces.example.com/v1/traces",
metricsEndpoint: "https://metrics.example.com/v1/metrics",
logsEndpoint: "https://logs.example.com/v1/logs",
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,
captureContent: {
enabled: false,
inputMessages: false,
outputMessages: false,
toolInputs: false,
toolOutputs: false,
systemPrompt: false,
},
},
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: no-progress age threshold in ms for classifying long-running processing sessions as session.long_running, session.stalled, or session.stuck. Reply, tool, status, block, and ACP progress reset the timer; repeated session.stuck diagnostics back off while unchanged.otel.enabled: enables the OpenTelemetry export pipeline (default: false). For the full configuration, signal catalog, and privacy model, see OpenTelemetry export.otel.endpoint: collector URL for OTel export.otel.tracesEndpoint / otel.metricsEndpoint / otel.logsEndpoint: optional signal-specific OTLP endpoints. When set, they override otel.endpoint for that signal only.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 0–1.otel.flushIntervalMs: periodic telemetry flush interval in ms.otel.captureContent: opt-in raw content capture for OTEL span attributes. Defaults to off. Boolean true captures non-system message/tool content; the object form lets you enable inputMessages, outputMessages, toolInputs, toolOutputs, and systemPrompt explicitly.OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental: environment toggle for latest experimental GenAI span provider attributes. By default spans keep the legacy gen_ai.system attribute for compatibility; GenAI metrics use bounded semantic attributes.OPENCLAW_OTEL_PRELOADED=1: environment toggle for hosts that already registered a global OpenTelemetry SDK. OpenClaw then skips plugin-owned SDK startup/shutdown while keeping diagnostic listeners active.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, and OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: signal-specific endpoint env vars used when the matching config key is unset.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: {
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: {
enabled: true,
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: true; set false to hide ACP dispatch and spawn affordances).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).
Install the backend plugin first, and if plugins.allow is set, include the backend plugin id (for example acpx) or the ACP backend will not load.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: {
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).OPENCLAW_HIDE_BANNER=1.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",
},
}
See agents.list identity fields under Agent defaults.
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: {
enabled: true,
maxConcurrentRuns: 2, // cron dispatch + isolated cron agent-turn execution
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: 0–10).backoffMs: array of backoff delays in ms for each retry attempt (default: [30000, 60000, 300000]; 1–10 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,
includeSkipped: false,
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).includeSkipped: count consecutive skipped runs toward the alert threshold (default: false). Skipped runs are tracked separately and do not affect execution-error backoff.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",
},
},
}
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.delivery.failureDestination overrides this global default.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.
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.) |
$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:
dirname of openclaw.json). Absolute/../ forms are allowed only when they still resolve inside that boundary.plugins install updates plugins: { $include: "./plugins.json5" } in plugins.json5 and leaves openclaw.json intact.Related: Configuration · Configuration Examples · Doctor