OpenClaw Configuration Reference
OpenClaw stores all configuration in ~/.openclaw/openclaw.json using JSON5 format (comments allowed, trailing commas OK). The gateway validates strictly on startup — unknown keys or wrong types prevent it from starting. This is the complete reference for every top-level object.
openclaw onboard — interactive first-time setup wizard
openclaw configure — config wizard for an existing install
openclaw config validate — check the file against the schema before restarting
openclaw config get agents.defaults.model — read a specific key
openclaw config set agents.defaults.heartbeat.every "2h" — set a value
openclaw config schema — view full JSON Schema
openclaw doctor — diagnose config problems
openclaw doctor --fix — auto-repair common issues
openclaw config validate — check the file before restarting the Gateway
- Multiple agents moved from
agents.listtoagents.entries, an object keyed by agent id, and the per-agentdefault: trueflag is retired. With more than one agent, a message that matches no binding now fails closed instead of falling back. Updated example below. - Startup migrates legacy keys only for single-file configs. Configs split with
$include, Nix-managed configs and configs written by a newer OpenClaw are not auto-migrated, so after an upgrade an old key can stop the Gateway from booting. Runopenclaw doctor --fix. Details. - Don't symlink
openclaw.json. OpenClaw writes the file atomically by renaming onto the path, so a symlink gets its target replaced rather than written through. - New in 2026.9.1:
cron.skipMissedJobsskips missed recurring jobs at startup;blockedHostnameson the SSRF policy blocks hosts for the browser, web fetch and webhooks;openclaw config setaccepts--dry-run,--strict-json,--expect-current-jsonand--expect-current-absent; andchannels.<id>.enabled: falseno longer loads that channel's plugin at all. - Retired in 2026.9.3:
skills.workshop.allowSymlinkTargetWrites. Workshop skills are now one writable collection per agent;openclaw doctor --fixmigrates skills with clear ownership. - The earlier version of this page showed an
agents.defaults.worktreeblock. That was a mistake —worktree.baseRefandworktree.bgIsolationare Claude Code settings, not OpenClaw ones, and OpenClaw's strict validation rejects the unknown key. Remove it if you copied it.
Top-Level Structure
The config file is a single JSON5 object. All top-level keys are optional — OpenClaw applies defaults for anything missing. OpenClaw follows a two-bucket rule: root-level keys hold infrastructure and cross-agent defaults, agents.defaults holds agent-loop behaviour, and agents.entries.<id> overrides either for one agent. The table lists the keys this page covers; OpenClaw's configuration reference has the rest (plugins, tools, browser, TTS and more), and openclaw config schema prints every field.
| Key | Purpose |
|---|---|
agents | Agent defaults, model list, skills, sandbox settings, heartbeat |
channels | Channel integrations: WhatsApp, Telegram, Discord, Slack and more |
session | Conversation scope, thread bindings, daily reset behaviour |
gateway | Server port, auth token, health monitoring, hot-reload mode |
cron | Global automation settings: enabled, missed-job handling, triggers, session retention, failure alerts |
hooks | Webhook endpoints, routing mappings, security tokens |
env | Environment variables, secrets, shell imports |
bindings | Which agent answers which channel, account or peer (multi-agent routing) |
plugins | Plugin entries and their settings, e.g. plugins.entries.brave |
ui | Web UI customisation |
broadcast | Multi-client configuration |
agents — Model, Skills & Sandbox
{
agents: {
defaults: {
workspace: "~/.openclaw/workspace",
// Primary model + fallbacks ("provider/model" ids — use your provider's current ones)
model: {
primary: "anthropic/claude-sonnet-5-5",
fallbacks: ["openai/gpt-6-sol"]
},
// Model allowlist — defines which models users can switch to
models: {
"anthropic/claude-sonnet-5-5": { alias: "Sonnet" },
"anthropic/claude-haiku-4-5": { alias: "Haiku" },
"openai/gpt-6-sol": { alias: "Sol" }
},
// Skills enabled by default for all agents
skills: ["github", "weather"], // omit for unrestricted skills
// Sandbox controls which tools run in isolation
sandbox: {
mode: "non-main", // off (default) | non-main | all
scope: "agent" // session | agent (default) | shared
},
// Heartbeat: proactive check-ins on a schedule
heartbeat: {
every: "30m", // duration string; "0m" disables the recurring cadence
target: "owner" // owner (default) | last | none | a channel id
}
},
// Named agents: an object keyed by agent id (was a "list" array before 2026.9)
entries: {
main: {
workspace: "~/.openclaw/workspace",
skills: ["github", "docs-search"], // replaces defaults.skills for this agent
groupChat: {
mentionPatterns: ["@openclaw", "openclaw"]
}
},
work: {
workspace: "~/.openclaw/workspace-work",
skills: ["github", "jira"]
}
}
}
}
A per-agent skills list replaces the defaults rather than adding to them, and [] means no skills. A string model on an agent is a strict primary with no fallback; use { primary, fallbacks: [...] } to opt that agent into fallback. The heartbeat object is strict — only its documented fields are accepted.
Sandbox Modes
| Mode | Behaviour |
|---|---|
off | No sandboxing — all skills run with full host access |
non-main | Non-primary agents run sandboxed; main agent runs direct (recommended) |
all | All agents sandboxed — most secure, slowest startup |
channels — All Integrations
Every channel uses the same DM access pattern. The key config fields are consistent across all providers:
{
channels: {
<provider>: {
enabled: true,
dmPolicy: "pairing", // pairing | allowlist | open | disabled
allowFrom: ["+15555550123"], // phone numbers, user IDs, or "*"
groupPolicy: "allowlist", // open | allowlist (default) | disabled
groups: {
"*": { requireMention: true }
}
}
}
}
dmPolicy Values
| Value | Behaviour | Use case |
|---|---|---|
pairing | An unknown sender messages the bot, gets a short code, and you approve it on the server with openclaw pairing approve | Personal use — most secure default |
allowlist | Only user IDs in allowFrom can DM the agent | Family/team where you know all IDs upfront |
open | Anyone who discovers the bot can message it | Public bots only — not recommended for personal agents |
disabled | DMs completely blocked; group-only access | Group-only deployments |
Telegram
{
channels: {
telegram: {
enabled: true,
botToken: "${TELEGRAM_BOT_TOKEN}",
dmPolicy: "pairing",
allowFrom: ["8734062810"], // your numeric Telegram user ID
groupPolicy: "allowlist",
groups: {
"-1001234567890": { // group chat ID (negative number)
requireMention: true,
allowFrom: ["8734062810", "745123456"]
}
}
}
}
}
{
channels: {
whatsapp: {
enabled: true,
dmPolicy: "allowlist",
allowFrom: ["+15555550123"], // E.164 format
groupPolicy: "allowlist"
}
}
}
Discord
{
channels: {
discord: {
enabled: true,
botToken: "${DISCORD_BOT_TOKEN}",
applicationId: "123456789012345678",
dmPolicy: "allowlist",
allowFrom: ["your-discord-user-id"]
}
}
}
session — Scope & Reset
{
session: {
// How conversation history is scoped
dmScope: "per-channel-peer",
// Options:
// main — one global session for all DMs
// per-peer — one session per sender (across channels)
// per-channel-peer — one session per sender per channel (recommended)
// per-account-channel-peer — adds account-level isolation
threadBindings: {
enabled: true,
idleHours: 24, // thread expires after 24h of inactivity
maxAgeHours: 0 // 0 = no hard limit
},
reset: {
mode: "daily", // daily | idle (omit, or "none", for no automatic reset)
atHour: 4, // 4 AM local time
idleMinutes: 120 // reset after 2h of no messages
}
}
}
gateway — Server Settings
{
gateway: {
port: 18789,
bind: "loopback", // default; non-loopback binds need gateway auth — keep it off public interfaces on a VPS
auth: {
token: "${OPENCLAW_GATEWAY_TOKEN}"
},
reload: {
mode: "hybrid" // hybrid (default) | off
}
},
// Health-monitor restarts can be switched off per channel or per account
channels: {
telegram: { healthMonitor: { enabled: true } }
}
}
Reload Modes
| Mode | Behaviour |
|---|---|
hybrid | Default. Applies hot-reloadable settings in-process and restarts the Gateway automatically when a change requires it |
off | Ignores live edits; changes need an explicit restart |
cron — Automations
The cron block sets global behaviour for scheduled automations, including the heartbeat. It is strict. Per OpenClaw's config reference, the only accepted keys are enabled, skipMissedJobs, triggers, webhookToken, webhookSsrfPolicy, sessionRetention and failureAlert. Anything else fails validation.
{
cron: {
enabled: true, // false pauses all automation execution (jobs are kept)
skipMissedJobs: false, // true: skip missed recurring slots at startup
triggers: { enabled: true },// event-driven triggers
sessionRetention: "24h", // how long completed isolated run sessions are kept ("0h" or false to disable)
failureAlert: { enabled: false, after: 2 }
}
}
Individual jobs aren't defined in config. Create them with the CLI, for example openclaw automations create "0 7 * * *" "Send me a morning brief" --name "Morning brief" --session isolated, and list them with openclaw automations list --all.
OpenClaw no longer reads HEARTBEAT.md. Heartbeat cadence is set by agents.defaults.heartbeat.every (default 30m, "0m" turns it off), and heartbeat instructions live in the heartbeat monitor's scratch: openclaw automations scratch <jobId> --set "…". If an old workspace still has HEARTBEAT.md, openclaw doctor --fix imports it and converts its task entries into cron jobs (docs).
Correction 2026-09-27: an earlier version of this section showed maxConcurrentRuns and runLog. They are not accepted keys, and pasting them breaks validation. It also said jobs are defined in HEARTBEAT.md.
Correction 2026-09-27: this page previously had an "event hooks — continueOnBlock" section (said to be "added in v2.1.139"). No such setting exists in OpenClaw; the version number was Claude Code's. The real top-level hooks block configures inbound webhook endpoints (for example Gmail), not behaviour on blocked tools.
env — Secrets & Environment Variables
{
env: {
// Inline values — only applied if the process environment is missing the key
vars: {
OPENROUTER_API_KEY: "sk-or-...",
GROQ_API_KEY: "gsk-..."
},
// Import from shell environment (most secure)
shellEnv: {
enabled: true,
timeoutMs: 15000
}
}
}
Reference env vars anywhere in the config with "${VAR_NAME}". Only uppercase names are supported. A missing or empty variable stays unresolved and logs a warning, and any setting that requires the value fails at startup or reload; add a fallback with "${VAR_NAME:-fallback}". Use openclaw doctor to diagnose.
The best practice is to use shellEnv: { enabled: true } and export your API keys in your shell profile (~/.zshrc or ~/.bashrc). This way the config file itself contains no secrets and can be safely version-controlled.
Multi-Agent Routing
Route different channels or accounts to different agents using bindings:
{
agents: {
ownership: "explicit",
// "default: true" is retired — name the agent that owns heartbeats and system work instead
defaults: { heartbeat: { agentId: "home" }, systemAgent: { agentId: "home" } },
entries: {
home: { workspace: "~/.openclaw/workspace-home" },
work: { workspace: "~/.openclaw/workspace-work" }
}
},
bindings: [
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
{ agentId: "work", match: { channel: "telegram", accountId: "*" } }
]
}
Bindings resolve in a fixed order: a specific match.peer first, then guildId, then teamId, then an exact accountId, then accountId: "*" (channel-wide). Within a tier the first matching binding wins. With more than one agent configured, a message that matches no binding is refused — only a single-agent setup falls back automatically — so add a channel-wide binding for anything you want answered.
Config Includes — Split Into Multiple Files
Large configs can be split across files using $include:
// ~/.openclaw/openclaw.json
{
agents: { $include: "./agents.json5" },
channels: { $include: "./channels.json5" },
broadcast: { $include: ["./clients/a.json5", "./clients/b.json5"] }
}
A single file replaces the object it is assigned to. An array of files deep-merges in order, later files overriding earlier ones, and sibling keys are merged after the includes. This lets you keep Telegram credentials in a separate file with tighter filesystem permissions.
$includeOn startup, OpenClaw automatically migrates renamed and retired keys — but only in single-file configs. A config split with $include is left as it is, and strict validation then stops the Gateway from booting if an old key remains (for example agents.list after the move to agents.entries). While it's down, only diagnostic commands work. Run openclaw doctor to see the exact problem and openclaw doctor --fix to repair it. OpenClaw keeps a last-known-good copy, but only doctor --fix restores it.
Official sources
Checked against the OpenClaw docs, release 2026.9.7, 2026-09-30. If anything here disagrees with these pages, trust the vendor docs.
← Back to OpenClaw hub · See also: Telegram Setup · Security Hardening · Cost Optimisation