Skip to main content

Config writes

Plugins can read the running configuration and — when justified — write openclaw.json through the same guarded writer the CLI uses. Before reaching for a global config write, check whether your own plugin config (api.pluginConfig, declared in your manifest) covers the need: it is scoped, validated, and never risks clobbering unrelated settings.

The contract

Config access on the plugin API:
register may be synchronous or async ((api: OpenClawPluginApi) => void | Promise<void>).

Prefer pluginConfig over global writes

If the values your plugin needs are yours alone, declare them in openclaw.plugin.json and read them from api.pluginConfig. The manifest schema is plain JSON Schema:
Users configure it under plugins.entries.my-plugin.config in openclaw.json, the control UI renders a form from your schema, and you never touch the global document. Capture the value in a closure at registration time — do not read api.pluginConfig from inside execute():

Writing global config

When a plugin genuinely must change global config (for example a channel onboarding flow), use api.runtime.config.writeConfigFile. It has the same guarantees as every other config write in the system:
  • Validation first. The document is validated against the full schema including plugin schemas. An invalid config throws before anything touches disk.
  • Top-level removal guard. If the write would drop a top-level key that exists on disk, it throws an error with code === "CONFIG_WRITE_REFUSED". Name intended removals in options.allowTopLevelKeyRemoval.
  • Atomic write with backups. Temp file plus rename; openclaw.json.bak plus a rotation ring of four more backups.
  • ${VAR} restoration. Unchanged environment-variable references in the existing file are preserved, not replaced with resolved values.

What not to do

Do not construct the next document from a stale snapshot of api.config and write it back wholesale:
Load immediately before the write, change the fewest paths possible, and let the writer handle the rest. Note there is no baseHash optimistic-concurrency check at this layer (that lives in the gateway RPC methods) — a plugin write is last-writer-wins at the file level, so keep the read-modify-write window short.

Restart and reload implications

A config write from a plugin lands on disk; the gateway file watcher then applies the reload policy:
  • Changes under your plugin’s own registration (installing or removing plugins, plugins.*) require a gateway restart to take effect.
  • Many operational paths (tools, agents, messages, cron, hooks, models) hot-reload or apply on the next turn under the default gateway.reload.mode: "hybrid".
  • If your plugin changes config at registration time, log a clear warning that a restart may be needed — the plugin runtime does not restart the gateway for you.

Async safety

writeConfigFile is async and performs file I/O with atomic rename — always await it, and never block the event loop around it:
A slow awaited write delays only your plugin’s registration. Synchronous blocking I/O in register() stalls the event loop for the whole gateway process during startup.