OpenClaw Gateway Configuration Overview and Quick Setup

Learn how OpenClaw loads its JSON5 config file, why symlinks are unsafe, and what you can set up like channels, models, and automation. This page guides new and existing users through the two-bucket config scheme.

Read this when

  • Setting up OpenClaw for the first time
  • Looking for common configuration patterns
  • Navigating to specific config sections

OpenClaw looks for an optional JSON5 configuration file at ~/.openclaw/openclaw.json. When that file is absent, the system falls back to safe defaults.

The path pointing to the active configuration must reference a regular file. Any writes performed by OpenClaw replace the file atomically, using a rename onto the path. Because of this, a symlinked openclaw.json will have its target swapped out rather than written through, so symlinked config layouts should be avoided. If your config lives outside the default state directory, set OPENCLAW_CONFIG_PATH directly to the real file.

Typical motivations for adding a config include:

  • Wiring up channels and restricting who can message the bot
  • Choosing models, tools, sandboxing, or automation such as cron and hooks
  • Adjusting sessions, media, networking, or the UI

Every available field is documented in the full reference.

Configuration follows a two-bucket scheme: root siblings carry infrastructure and cross-agent defaults, while agents.defaults holds agent-loop behavior. Where the schema permits a per-agent override, entries under agents.entries may override either bucket.

Before editing config, agents and automation should rely on config.schema.lookup for exact field-level docs. This page offers task-oriented guidance, while the Configuration reference provides the broader field map and defaults.

Tip

New to configuration? Try openclaw onboard for interactive setup, or look at the Configuration Examples guide for ready-to-paste configs.

Minimal config

// ~/.openclaw/openclaw.json
{
  agents: { defaults: { workspace: "~/.openclaw/workspace" } },
  channels: { whatsapp: { allowFrom: ["+15555550123"] } },
}

Editing config

Interactive wizard

openclaw onboard       # full onboarding flow
openclaw configure     # config wizard

CLI (one-liners)

openclaw config get agents.defaults.workspace
openclaw config set agents.defaults.heartbeat.every "2h"
openclaw config unset plugins.entries.brave.config.webSearch.apiKey

Control UI

Navigate to http://127.0.0.1:18789 and open the Config tab. From the live config schema, the Control UI renders a form that includes field title / description docs metadata plus plugin and channel schemas when they are available, with a Raw JSON editor available as an escape hatch. For drill-down UIs and other tooling, the gateway also exposes config.schema.lookup to retrieve one path-scoped schema node along with immediate child summaries. Common fields appear first in settings. Each section keeps its advanced fields in a collapsed Advanced (N) group; the Show advanced option expands all groups at once. Settings search covers both tiers and opens the matching advanced group when needed. Per-channel settings under Settings -> Channels use the same split and honor the Show advanced preference, with Hide advanced on the divider to collapse them again.

Direct edit

Modify ~/.openclaw/openclaw.json directly. The Gateway watches the file and applies changes automatically, as described in hot reload.

Strict validation

Warning

OpenClaw only accepts configurations that fully match the schema. Unknown keys, malformed types, or invalid values cause the Gateway to refuse to start. The only root-level exception is $schema (string), so editors can attach JSON Schema metadata.

The canonical JSON Schema used by Control UI and validation is printed by openclaw config schema. For drill-down tooling, config.schema.lookup fetches a single path-scoped node plus child summaries. Field title/description docs metadata carries through nested objects, wildcard (*), array-item ([]), and anyOf/oneOf/allOf branches. Runtime plugin and channel schemas merge in when the manifest registry is loaded.

In uiHints, every config leaf has a common or advanced presentation tier. Common settings are marked by advanced: false, while advanced: true marks advanced settings. When a leaf has no direct hint, it inherits the nearest ancestor tier; paths with no declared ancestor default to advanced. This affects presentation only, not validation, defaults, reload behavior, or whether the key can be set.

When validation fails:

  • The Gateway does not boot
  • Only diagnostic commands work (openclaw doctor, openclaw logs, openclaw health, openclaw status)
  • Run openclaw doctor to see exact issues
  • Run openclaw doctor --fix (--repair is the same flag; --yes skips prompts) to apply repairs

After each successful startup, the Gateway keeps a trusted last-known-good copy, but startup and hot reload do not restore it automatically, only openclaw doctor --fix does. If openclaw.json fails validation, including plugin-local validation, Gateway startup fails or the reload is skipped and the current runtime keeps the last accepted config. A rejected write is also saved as <path>.rejected.<timestamp> for inspection. Writes that look like accidental clobbers are blocked by the Gateway, such as dropping gateway.mode, losing the meta block, or shrinking the file by more than half, unless the write explicitly allows destructive changes. Promotion to last-known-good is skipped when a candidate contains a redacted secret placeholder such as *** or [redacted].

Common tasks

Set up a channel (WhatsApp, Telegram, Discord, etc.)

Each channel has its own config section under channels.<provider>. See the dedicated channel page for setup steps:

The DM policy pattern is identical across every channel:

{
  channels: {
    telegram: {
      enabled: true,
      botToken: "123:abc",
      dmPolicy: "pairing",   // pairing | allowlist | open | disabled
      allowFrom: ["tg:123"], // only for allowlist/open
    },
  },
}

Choose and configure models

Define the primary model, with optional fallbacks:

{
  agents: {
    defaults: {
      model: {
        primary: "anthropic/claude-sonnet-4-6",
        fallbacks: ["openai/gpt-5.4"],
      },
      models: {
        "anthropic/claude-sonnet-4-6": { alias: "Sonnet" },
        "openai/gpt-5.4": { alias: "GPT" },
      },
    },
  },
}
  • Aliases and per-model configurations live in agents.defaults.models; adding an entry never blocks /model or --model overrides.
  • The explicit allowlist for overrides and model pickers is agents.defaults.modelPolicy.allow. It accepts exact refs and provider/* wildcards; leaving it out or setting [] permits any model.
  • Model references follow the provider/model format, for example anthropic/claude-opus-4-6.
  • Transcript and tool image downscaling is managed by agents.defaults.imageMaxDimensionPx (default 1200); reducing this value often cuts vision-token usage in screenshot-heavy workloads.
  • To switch models mid-chat, check the Models CLI; for auth rotation and fallback behavior, see Model Failover.
  • Custom or self-hosted providers are covered in Custom providers within the reference.

Control who can message the bot

Per-channel DM access is governed by dmPolicy (default "pairing"):

  • "pairing": first-time senders receive a one-time pairing code for approval
  • "allowlist": only those listed in allowFrom (or the paired allow store) can message
  • "open": all inbound DMs are accepted (requires allowFrom: ["*"])
  • "disabled": DMs are completely ignored

For group scenarios, combine groupPolicy ("allowlist" | "open" | "disabled") with groupAllowFrom or channel-specific allowlists.

Per-channel specifics appear in the full reference.

Set up group chat mention gating

Group messages are set to require mention by default. Trigger patterns are configured per agent. Standard group or channel replies are posted automatically; shared rooms where the agent should decide when to speak should opt into the message-tool path:

{
  messages: {
    visibleReplies: "automatic", // set "message_tool" to require message-tool sends everywhere
    groupChat: {
      visibleReplies: "message_tool", // opt-in; visible output requires message(action=send)
      unmentionedInbound: "room_event", // unmentioned always-on group chatter is quiet context
    },
  },
  agents: {
    entries: {
      main: {
        default: true,
        groupChat: {
          mentionPatterns: ["@openclaw", "openclaw"],
        },
      },
    },
  },
  channels: {
    whatsapp: {
      groups: { "*": { requireMention: true } },
    },
  },
}
  • Metadata mentions: native @-mentions (WhatsApp tap-to-mention, Telegram @bot, etc.)
  • Text patterns: safe regex patterns defined in mentionPatterns
  • Visible replies: messages.visibleReplies can force message-tool sends globally; messages.groupChat.visibleReplies overrides that for groups or channels.
  • For visible reply modes, per-channel overrides, and self-chat mode, see the full reference.

Restrict skills per agent

Use agents.defaults.skills as a shared baseline, then apply per-agent overrides with agents.entries.*.skills:

{
  agents: {
    defaults: {
      skills: ["github", "weather"],
    },
    entries: {
      writer: { default: true }, // inherits github, weather
      docs: { skills: ["docs-search"] }, // replaces defaults
      "locked-down": { skills: [] }, // no skills
    },
  },
}
  • Leave agents.defaults.skills out to allow all skills by default.
  • Drop agents.entries.*.skills to apply the standard defaults.
  • Use agents.entries.*.skills: [] to disable skills entirely.
  • Refer to Skills, Skills config, and the Configuration Reference.

Configure per-channel health monitoring

Turn automatic health restarts on or off for a given channel or account:

{
  channels: {
    telegram: {
      healthMonitor: { enabled: false },
      accounts: {
        alerts: {
          healthMonitor: { enabled: true },
        },
      },
    },
  },
}
  • Control auto-restart behavior with channels.<provider>.healthMonitor.enabled or channels.<provider>.accounts.<id>.healthMonitor.enabled for a single channel or account.
  • For operational debugging, check Health Checks; the full reference lists every field.

Configure sessions and resets

Sessions determine how conversation continuity and isolation behave:

{
  session: {
    dmScope: "per-channel-peer",  // recommended for multi-user
    threadBindings: {
      enabled: true,
      idleHours: 24,
      maxAgeHours: 0,
    },
    reset: {
      mode: "daily",
      atHour: 4,
      idleMinutes: 120,
    },
  },
}
  • dmScope: main (shared) | per-peer | per-channel-peer | per-account-channel-peer
  • threadBindings: global defaults for routing sessions by thread. Per-session binding, unbinding, listing, and tuning happen through /focus, /unfocus, /agents, /session idle, and /session max-age (Discord binds threads, Telegram binds topics/conversations).
  • See Session Management for scoping, identity links, and send policy.
  • All fields are documented in the full reference.

Enable sandboxing

Run agent sessions inside isolated sandbox runtimes:

{
  agents: {
    defaults: {
      sandbox: {
        mode: "non-main",  // off | non-main | all
        scope: "agent",    // session | agent | shared
      },
    },
  },
}

Build the image first: run scripts/sandbox-setup.sh from a source checkout, or for an npm install use the inline docker build command in Sandboxing § Images and setup.

The complete guide lives in Sandboxing, and every option is covered in the full reference.

Enable relay-backed push for official iOS builds

For public App Store builds, relay-backed push goes through the hosted OpenClaw relay: https://ios-push-relay.openclaw.ai.

A custom relay deployment needs a separate iOS build and deployment path whose relay URL matches the gateway relay URL. If you run a custom relay build, set this in the gateway config:

{
  gateway: {
    push: {
      apns: {
        relay: {
          baseUrl: "https://relay.example.com",
          // Optional. Default: 10000
          timeoutMs: 10000,
        },
      },
    },
  },
}

CLI equivalent:

openclaw config set gateway.push.apns.relay.baseUrl https://relay.example.com

What this does:

  • Enables the gateway to send push.test, wake nudges, and reconnect wakes through the external relay.
  • Uses a registration-scoped send grant forwarded by the paired iOS app, so no deployment-wide relay token is needed on the gateway.
  • Ties each relay-backed registration to the gateway identity the iOS app paired with, preventing another gateway from reusing the stored registration.
  • Leaves local and manual iOS builds on direct APNs. Relay-backed sends only apply to official distributed builds that registered through the relay.
  • Must align with the relay base URL baked into the iOS build so registration and send traffic hit the same relay deployment.

End-to-end flow:

  1. Install the official iOS app.
  2. Optional: set gateway.push.apns.relay.baseUrl on the gateway only when using a deliberately separate custom relay build.
  3. Pair the iOS app to the gateway and let both node and operator sessions connect.
  4. The iOS app fetches the gateway identity, registers with the relay using App Attest plus the app receipt, and then publishes the relay-backed push.apns.register payload to the paired gateway.
  5. The gateway stores the relay handle and send grant, then uses them for push.test, wake nudges, and reconnect wakes.

Operational notes:

  • If you point the iOS app at a different gateway, reconnect the app so it can publish a new relay registration bound to that gateway.
  • If you ship a new iOS build that targets a different relay deployment, the app refreshes its cached relay registration instead of reusing the old relay origin.

Compatibility note:

  • OPENCLAW_APNS_RELAY_BASE_URL and OPENCLAW_APNS_RELAY_TIMEOUT_MS still work as temporary env overrides.
  • Custom gateway relay URLs must match the relay base URL baked into the iOS build; the public App Store release lane rejects custom iOS relay URL overrides.
  • OPENCLAW_APNS_RELAY_ALLOW_HTTP=true remains a loopback-only development escape hatch; do not persist HTTP relay URLs in config.

The end-to-end flow is described in iOS App, and the relay security model is covered in Authentication and trust flow.

Set up heartbeat (periodic check-ins)

{
  agents: {
    defaults: {
      heartbeat: {
        every: "30m",
        target: "owner",
      },
    },
  },
}
  • every: a duration string (30m, 2h). Assign 0m to turn off recurring cadence; event-driven targeted wakes may still execute a single agent turn. Default: 30m.
  • target: owner (default operator DM) | last (most recent conversation, including groups) | none (internal only) | <channel-id>
  • directPolicy: allow (default) or block for DM-style heartbeat targets
  • Consult the Heartbeat guide for complete details.

Configure cron jobs

{
  cron: {
    enabled: true,
    sessionRetention: "24h",
  },
}
  • sessionRetention: remove completed isolated run sessions from SQLite session rows (default 24h; use false or a zero duration such as "0h" to disable).
  • Terminal run history stays for 7 days (lost rows for 24 hours), with the newest 2000 rows per job and history class enforced as an extra cap.
  • Refer to Cron jobs for an overview and CLI examples.

Set up webhooks (hooks)

Activate HTTP webhook endpoints on the Gateway:

{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
    defaultSessionKey: "hook:ingress",
    allowRequestSessionKey: false,
    allowedSessionKeyPrefixes: ["hook:"],
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        agentId: "main",
        sessionKey: "hook:gmail",
        sessionMode: "persistent",
        deliver: true,
      },
    ],
  },
}

Security note:

  • Consider all hook/webhook payload content as untrusted.
  • Employ a dedicated hooks.token; avoid reusing active Gateway auth secrets (gateway.auth.token / OPENCLAW_GATEWAY_TOKEN or gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD).
  • Hook auth relies solely on headers (Authorization: Bearer ... or x-openclaw-token); query-string tokens get rejected.
  • hooks.path cannot be /; keep webhook ingress on a dedicated subpath such as /hooks.
  • Leave unsafe-content bypass flags off (hooks.gmail.allowUnsafeExternalContent, hooks.mappings[].allowUnsafeExternalContent) unless debugging in a tightly scoped manner.
  • When hooks.allowRequestSessionKey is enabled, also configure hooks.allowedSessionKeyPrefixes to limit caller-selected session keys.
  • Keep hook sessions isolated unless durable context is deliberate. Direct persistent hooks need an explicit, prefix-bounded request sessionKey; mapped persistent hooks require a stable mapping key or hooks.defaultSessionKey.
  • For hook-driven agents, choose strong modern model tiers and strict tool policy (for example messaging-only plus sandboxing where feasible).

See the full reference for all mapping options and Gmail integration.

Configure multi-agent routing

Run multiple isolated agents with separate workspaces and sessions:

{
  agents: {
    entries: {
      home: { default: true, workspace: "~/.openclaw/workspace-home" },
      work: { workspace: "~/.openclaw/workspace-work" },
    },
  },
  bindings: [
    { agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
    { agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
  ],
}

See Multi-Agent and the full reference for binding rules and per-agent access profiles.

Split config into multiple files ($include)

Use $include to organize large configs:

// ~/.openclaw/openclaw.json
{
  gateway: { port: 18789 },
  agents: { $include: "./agents.json5" },
  broadcast: {
    $include: ["./clients/a.json5", "./clients/b.json5"],
  },
}
  • Single file: replaces the containing object
  • Array of files: deep-merged in order (later wins), up to 10 nested levels deep
  • Sibling keys: merged after includes (override included values)
  • Relative paths: resolved relative to the including file
  • Path format: include paths must not contain null bytes and must be strictly shorter than 4096 characters before and after resolution
  • OpenClaw-owned writes: when a write changes only one top-level section backed by a single-file include such as plugins: { $include: "./plugins.json5" }, OpenClaw updates that included file and leaves openclaw.json intact
  • Unsupported write-through: root includes, include arrays, and includes with sibling overrides fail closed for OpenClaw-owned writes instead of flattening the config
  • Confinement: $include paths must resolve under the directory holding openclaw.json. To share a tree across machines or users, set OPENCLAW_INCLUDE_ROOTS to a path-list (: on POSIX, ; on Windows) of additional directories that includes may reference. Symlinks are resolved and re-checked, so a path that lexically lives in a config dir but whose real target escapes every allowed root is still rejected.
  • Error handling: clear errors for missing files, parse errors, circular includes, invalid path format, and excessive length

Config hot reload

The Gateway watches ~/.openclaw/openclaw.json and applies changes automatically - no manual restart needed for most settings.

Direct file edits are treated as untrusted until they validate. The watcher waits for editor temp-write/rename churn to settle, reads the final file, and rejects invalid external edits without rewriting openclaw.json. OpenClaw-owned config writes use the same schema gate before writing (see Strict validation for the clobber/rollback rules that apply to every write).

If you encounter config reload skipped (invalid config) or the startup process reports Invalid config, check the configuration, execute openclaw config validate, and then run openclaw doctor --fix to fix the issue. The full checklist lives in Gateway troubleshooting.

Reload modes

ModeBehavior
hybrid (default)Safe changes are applied immediately without downtime. Critical ones trigger an automatic restart.
offFile watching is turned off. Changes only take effect when you manually restart later.
{
  gateway: {
    reload: { mode: "hybrid" },
  },
}

The older hot and restart modes have been removed; openclaw doctor --fix translates both into hybrid. Debounce for reloads is now fixed and cannot be adjusted, operating under a built-in default.

What hot-applies vs what needs a restart

The majority of fields take effect immediately without any downtime; a few hot-applied sections restart only their own subsystem (channel, cron, heartbeat, health monitor) rather than the entire Gateway. Under hybrid mode, changes that would normally require a Gateway restart are managed automatically.

CategoryFieldsGateway restart needed?
Channelschannels.*, web (WhatsApp) - all built-in and plugin channelsNo (restarts that channel)
Agent & modelsagent, agents, models, routingNo
Automationhooks, cron, agent.heartbeatNo (restarts that subsystem)
Sessions & messagessession, messagesNo
Tools & mediatools, skills, mcp, audio, talkNo
Plugin configplugins.entries.*, plugins.allow, plugins.deny, plugins.enabledNo (reloads plugin runtime)
UI & miscui, logging, identity, bindingsNo
Gateway servergateway.* (port, bind, auth, tailscale, TLS, HTTP, push)Yes
Infrastructurediscovery, browser, plugins.load, plugins.installsYes

Note

gateway.reload and gateway.remote break the pattern under gateway.*: modifying them does not force a restart. Plugins can also override this table on their own, since a loaded plugin may list its own config prefixes that trigger restarts (the bundled Canvas plugin, for instance, restarts the Gateway for plugins.enabled, plugins.allow, and plugins.deny, not just its own plugins.entries.canvas). What actually happens therefore hinges on which plugins are active.

Reload planning

When a source file referenced through $include gets edited, OpenClaw calculates the reload based on the layout as authored, not the flattened in-memory version. This keeps hot-reload choices (hot-apply versus restart) consistent even when a top-level section lives in its own separate file like plugins: { $include: "./plugins.json5" }. If the source layout is ambiguous, reload planning fails closed.

Config RPC (programmatic updates)

For tooling that writes config over the gateway API, prefer this flow:

  • config.schema.lookup to look at a single subtree (the shallow schema node together with its child summaries)
  • config.get to retrieve the current snapshot along with hash
  • config.patch for incremental edits (JSON merge patch: objects combine, null removes, arrays swap only when explicitly confirmed through replacePaths if entries stand to be dropped)
  • config.apply solely when your intent is to swap in an entirely new configuration
  • update.run for an explicit self-update followed by a restart; add continuationMessage when the session after restart should carry out one additional turn
  • update.status to check the latest update restart sentinel and confirm the running version once a restart completes

Agents should treat config.schema.lookup as the initial reference for precise field-level documentation and constraints. Point them to Configuration reference when the full config map, defaults, or links to subsystem-specific references are what they need.

Note

Control-plane writes (config.apply, config.patch, update.run) are throttled to 30 requests per 60 seconds, per method, per deviceId+clientIp; consult Rate limiting. Restart requests merge together and then impose a 30-second gap between restart cycles. update.status is read-only yet admin-scoped, since the restart sentinel may carry update step summaries and command output tails.

A sample partial patch:

openclaw gateway call config.get --params '{}'  # capture payload.hash
openclaw gateway call config.patch --params '{
  "raw": "{ channels: { telegram: { groups: { \"*\": { requireMention: false } } } } }",
  "baseHash": "<hash>"
}'

Both config.apply and config.patch take raw, baseHash, sessionKey, note, and restartDelayMs. Once a config file is already present, baseHash becomes mandatory for both methods (a first write without an existing config bypasses the check).

config.patch additionally accepts replacePaths, an array of config paths whose array replacement is deliberate. When a patch would swap or remove an existing array for one with fewer entries, the Gateway blocks the write unless that exact path appears in replacePaths; nested arrays beneath array entries rely on [], for instance agents.entries.*.skills. This stops truncated config.get snapshots from quietly overwriting routing or allowlist arrays. Use config.apply when a full config replacement is your goal.

Environment variables

OpenClaw picks up env vars from the parent process plus:

  • .env sourced from the current working directory (if it exists)
  • ~/.openclaw/.env (system-wide fallback)

Neither file overrides existing env vars. Inline env vars can also be set inside config:

{
  env: {
    vars: {
      OPENROUTER_API_KEY: "sk-or-...",
      GROQ_API_KEY: "gsk-...",
    },
  },
}

Shell env import (optional)

When enabled and expected keys are absent, OpenClaw launches your login shell and imports only the missing keys:

{
  env: {
    shellEnv: { enabled: true, timeoutMs: 15000 },
  },
}

Env var counterpart: OPENCLAW_LOAD_SHELL_ENV=1. Default timeoutMs: 15000.

Env var substitution in config values

Use ${VAR_NAME} to reference env vars in any config string value:

{
  gateway: { auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" } },
  models: { providers: { custom: { apiKey: "${CUSTOM_API_KEY}" } } },
}

Rules:

  • Only uppercase names are matched: [A-Z_][A-Z0-9_]*
  • Missing or empty vars stay visibly unresolved, trigger a warning, and remain unavailable to consumers that need the value
  • Escape with $${VAR} to yield a literal ${VAR} value
  • Functions inside $include files
  • Inline substitution: "${BASE}/v1""https://api.example.com/v1"

Secret refs (env, file, exec, store)

For fields supporting SecretRef objects, this option is available:

{
  models: {
    providers: {
      openai: { apiKey: { source: "env", provider: "default", id: "OPENAI_API_KEY" } },
    },
  },
  skills: {
    entries: {
      "image-lab": {
        apiKey: {
          source: "file",
          provider: "filemain",
          id: "/skills/entries/image-lab/apiKey",
        },
      },
    },
  },
  channels: {
    googlechat: {
      serviceAccount: {
        source: "exec",
        provider: "vault",
        id: "channels/googlechat/serviceAccount",
      },
    },
  },
}

SecretRef specifics, including secrets.providers for env/file/exec/store, are covered under Secrets Management.
The list of supported credential paths appears in SecretRef Credential Surface.

For a complete breakdown of precedence and sources, refer to Environment.

Full reference

A full field-by-field reference is available in Configuration Reference.


Related: Configuration Examples · Configuration Reference · Doctor

3,903 words · updated Aug 25, 2026