The Control UI is a small Vite + Lit single-page app served by the Gateway:Documentation Index
Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt
Use this file to discover all available pages before exploring further.
- default:
http://<host>:18789/ - optional prefix: set
gateway.controlUi.basePath(e.g./openclaw)
Quick open (local)
If the Gateway is running on the same computer, open: If the page fails to load, start the Gateway first:openclaw gateway.
Auth is supplied during the WebSocket handshake via:
connect.params.auth.tokenconnect.params.auth.password- Tailscale Serve identity headers when
gateway.auth.allowTailscale: true - trusted-proxy identity headers when
gateway.auth.mode: "trusted-proxy"
gateway.auth.mode is "password".
Device pairing (first connection)
When you connect to the Control UI from a new browser or device, the Gateway usually requires a one-time pairing approval. This is a security measure to prevent unauthorized access. What you’ll see: “disconnected (1008): pairing required” If the browser retries pairing with changed auth details (role/scopes/public key), the previous pending request is superseded and a newrequestId is created. Re-run openclaw devices list before approval.
If the browser is already paired and you change it from read access to write/admin access, this is treated as an approval upgrade, not a silent reconnect. OpenClaw keeps the old approval active, blocks the broader reconnect, and asks you to approve the new scope set explicitly.
Once approved, the device is remembered and won’t require re-approval unless you revoke it with openclaw devices revoke --device <id> --role <role>. See Devices CLI for token rotation and revocation.
- Direct local loopback browser connections (
127.0.0.1/localhost) are auto-approved. - Tailscale Serve can skip the pairing round trip for Control UI operator sessions when
gateway.auth.allowTailscale: true, Tailscale identity verifies, and the browser presents its device identity. - Direct Tailnet binds, LAN browser connects, and browser profiles without device identity still require explicit approval.
- Each browser profile generates a unique device ID, so switching browsers or clearing browser data will require re-pairing.
Personal identity (browser-local)
The Control UI supports a per-browser personal identity (display name and avatar) attached to outgoing messages for attribution in shared sessions. It lives in browser storage, is scoped to the current browser profile, and is not synced to other devices or persisted server-side beyond the normal transcript authorship metadata on messages you actually send. Clearing site data or switching browsers resets it to empty. The same browser-local pattern applies to the assistant avatar override. Uploaded assistant avatars overlay the gateway-resolved identity on the local browser only and never round-trip throughconfig.patch. The shared ui.assistant.avatar config field is still available for non-UI clients writing the field directly (such as scripted gateways or custom dashboards).
Runtime config endpoint
The Control UI fetches its runtime settings from/__openclaw/control-ui-config.json. That endpoint is gated by the same gateway auth as the rest of the HTTP surface: unauthenticated browsers cannot fetch it, and a successful fetch requires either an already valid gateway token/password, Tailscale Serve identity, or a trusted-proxy identity.
Language support
The Control UI can localize itself on first load based on your browser locale. To override it later, open Overview -> Gateway Access -> Language. The locale picker lives in the Gateway Access card, not under Appearance.- Supported locales:
en,zh-CN,zh-TW,pt-BR,de,es,ja-JP,ko,fr,tr,uk,id,pl,th - Non-English translations are lazy-loaded in the browser.
- The selected locale is saved in browser storage and reused on future visits.
- Missing translation keys fall back to English.
Appearance themes
The Appearance panel keeps the built-in Claw, Knot, and Dash themes, plus one browser-local tweakcn import slot. To import a theme, open tweakcn themes, choose or create a theme, click Share, and paste the copied theme link into Appearance. The importer also acceptshttps://tweakcn.com/r/themes/<id> registry URLs, editor URLs like https://tweakcn.com/editor/theme?theme=amethyst-haze, relative /themes/<id> paths, raw theme IDs, and default theme names such as amethyst-haze.
Imported themes are stored only in the current browser profile. They are not written to gateway config and do not sync across devices. Replacing the imported theme updates the one local slot; clearing it switches the active theme back to Claw if the imported theme was selected.
What it can do (today)
Chat and Talk
Chat and Talk
- Chat with the model via Gateway WS (
chat.history,chat.send,chat.abort,chat.inject). - Talk through browser realtime sessions. OpenAI uses direct WebRTC, Google Live uses a constrained one-use browser token over WebSocket, and backend-only realtime voice plugins use the Gateway relay transport. The relay keeps provider credentials on the Gateway while the browser streams microphone PCM through
talk.realtime.relay*RPCs and sendsopenclaw_agent_consulttool calls back throughchat.sendfor the larger configured OpenClaw model. - Stream tool calls + live tool output cards in Chat (agent events).
Channels, instances, sessions, dreams
Channels, instances, sessions, dreams
- Channels: built-in plus bundled/external plugin channels status, QR login, and per-channel config (
channels.status,web.login.*,config.patch). - Instances: presence list + refresh (
system-presence). - Sessions: list + per-session model/thinking/fast/verbose/trace/reasoning overrides (
sessions.list,sessions.patch). - Dreams: dreaming status, enable/disable toggle, and Dream Diary reader (
doctor.memory.status,doctor.memory.dreamDiary,config.patch).
Cron, skills, nodes, exec approvals
Cron, skills, nodes, exec approvals
- Cron jobs: list/add/edit/run/enable/disable + run history (
cron.*). - Skills: status, enable/disable, install, API key updates (
skills.*). - Nodes: list + caps (
node.list). - Exec approvals: edit gateway or node allowlists + ask policy for
exec host=gateway/node(exec.approvals.*).
Config
Config
- View/edit
~/.openclaw/openclaw.json(config.get,config.set). - Apply + restart with validation (
config.apply) and wake the last active session. - Writes include a base-hash guard to prevent clobbering concurrent edits.
- Writes (
config.set/config.apply/config.patch) preflight active SecretRef resolution for refs in the submitted config payload; unresolved active submitted refs are rejected before write. - Schema + form rendering (
config.schema/config.schema.lookup, including fieldtitle/description, matched UI hints, immediate child summaries, docs metadata on nested object/wildcard/array/composition nodes, plus plugin + channel schemas when available); Raw JSON editor is available only when the snapshot has a safe raw round-trip. - If a snapshot cannot safely round-trip raw text, Control UI forces Form mode and disables Raw mode for that snapshot.
- Raw JSON editor “Reset to saved” preserves the raw-authored shape (formatting, comments,
$includelayout) instead of re-rendering a flattened snapshot, so external edits survive a reset when the snapshot can safely round-trip. - Structured SecretRef object values are rendered read-only in form text inputs to prevent accidental object-to-string corruption.
Debug, logs, update
Debug, logs, update
- Debug: status/health/models snapshots + event log + manual RPC calls (
status,health,models.list). - Logs: live tail of gateway file logs with filter/export (
logs.tail). - Update: run a package/git update + restart (
update.run) with a restart report, then pollupdate.statusafter reconnect to verify the running gateway version.
Cron jobs panel notes
Cron jobs panel notes
- For isolated jobs, delivery defaults to announce summary. You can switch to none if you want internal-only runs.
- Channel/target fields appear when announce is selected.
- Webhook mode uses
delivery.mode = "webhook"withdelivery.toset to a valid HTTP(S) webhook URL. - For main-session jobs, webhook and none delivery modes are available.
- Advanced edit controls include delete-after-run, clear agent override, cron exact/stagger options, agent model/thinking overrides, and best-effort delivery toggles.
- Form validation is inline with field-level errors; invalid values disable the save button until fixed.
- Set
cron.webhookTokento send a dedicated bearer token, if omitted the webhook is sent without an auth header. - Deprecated fallback: stored legacy jobs with
notify: truecan still usecron.webhookuntil migrated.
Chat behavior
Send and history semantics
Send and history semantics
chat.sendis non-blocking: it acks immediately with{ runId, status: "started" }and the response streams viachatevents.- Chat uploads accept images plus non-video files. Images keep the native image path; other files are stored as managed media and shown in history as attachment links.
- Re-sending with the same
idempotencyKeyreturns{ status: "in_flight" }while running, and{ status: "ok" }after completion. chat.historyresponses are size-bounded for UI safety. When transcript entries are too large, Gateway may truncate long text fields, omit heavy metadata blocks, and replace oversized messages with a placeholder ([chat.history omitted: message too large]).- Assistant/generated images are persisted as managed media references and served back through authenticated Gateway media URLs, so reloads do not depend on raw base64 image payloads staying in the chat history response.
chat.historyalso strips display-only inline directive tags from visible assistant text (for example[[reply_to_*]]and[[audio_as_voice]]), plain-text tool-call XML payloads (including<tool_call>...</tool_call>,<function_call>...</function_call>,<tool_calls>...</tool_calls>,<function_calls>...</function_calls>, and truncated tool-call blocks), and leaked ASCII/full-width model control tokens, and omits assistant entries whose whole visible text is only the exact silent tokenNO_REPLY/no_reply.- During an active send and the final history refresh, the chat view keeps local optimistic user/assistant messages visible if
chat.historybriefly returns an older snapshot; the canonical transcript replaces those local messages once the Gateway history catches up. chat.injectappends an assistant note to the session transcript and broadcasts achatevent for UI-only updates (no agent run, no channel delivery).- The chat header model and thinking pickers patch the active session immediately through
sessions.patch; they are persistent session overrides, not one-turn-only send options. - The chat model picker requests the Gateway’s configured model view. If
agents.defaults.modelsis present, that allowlist drives the picker. Otherwise the picker shows explicitmodels.providers.*.modelsentries before falling back to the full catalog for fresh installs. - When fresh Gateway session usage reports show high context pressure, the chat composer area shows a context notice and, at recommended compaction levels, a compact button that runs the normal session compaction path. Stale token snapshots are hidden until the Gateway reports fresh usage again.
Talk mode (browser realtime)
Talk mode (browser realtime)
Talk mode uses a registered realtime voice provider. Configure OpenAI with
talk.provider: "openai" plus talk.providers.openai.apiKey, or configure Google with talk.provider: "google" plus talk.providers.google.apiKey; Voice Call realtime provider config can still be reused as the fallback. The browser never receives a standard provider API key. OpenAI receives an ephemeral Realtime client secret for WebRTC. Google Live receives a one-use constrained Live API auth token for a browser WebSocket session, with instructions and tool declarations locked into the token by the Gateway. Providers that only expose a backend realtime bridge run through the Gateway relay transport, so credentials and vendor sockets stay server-side while browser audio moves through authenticated Gateway RPCs. The Realtime session prompt is assembled by the Gateway; talk.realtime.session does not accept caller-provided instruction overrides.In the Chat composer, the Talk control is the waves button next to the microphone dictation button. When Talk starts, the composer status row shows Connecting Talk..., then Talk live while audio is connected, or Asking OpenClaw... while a realtime tool call is consulting the configured larger model through chat.send.Maintainer live smoke: OPENAI_API_KEY=... GEMINI_API_KEY=... node --import tsx scripts/dev/realtime-talk-live-smoke.ts verifies the OpenAI browser WebRTC SDP exchange, Google Live constrained-token browser WebSocket setup, and the Gateway relay browser adapter with fake microphone media. The command prints provider status only and does not log secrets.Stop and abort
Stop and abort
- Click Stop (calls
chat.abort). - While a run is active, normal follow-ups queue. Click Steer on a queued message to inject that follow-up into the running turn.
- Type
/stop(or standalone abort phrases likestop,stop action,stop run,stop openclaw,please stop) to abort out-of-band. chat.abortsupports{ sessionKey }(norunId) to abort all active runs for that session.
Abort partial retention
Abort partial retention
- When a run is aborted, partial assistant text can still be shown in the UI.
- Gateway persists aborted partial assistant text into transcript history when buffered output exists.
- Persisted entries include abort metadata so transcript consumers can tell abort partials from normal completion output.
PWA install and web push
The Control UI ships amanifest.webmanifest and a service worker, so modern browsers can install it as a standalone PWA. Web Push lets the Gateway wake the installed PWA with notifications even when the tab or browser window is not open.
| Surface | What it does |
|---|---|
ui/public/manifest.webmanifest | PWA manifest. Browsers offer “Install app” once it is reachable. |
ui/public/sw.js | Service worker that handles push events and notification clicks. |
push/vapid-keys.json (under the OpenClaw state dir) | Auto-generated VAPID keypair used to sign Web Push payloads. |
push/web-push-subscriptions.json | Persisted browser subscription endpoints. |
OPENCLAW_VAPID_PUBLIC_KEYOPENCLAW_VAPID_PRIVATE_KEYOPENCLAW_VAPID_SUBJECT(defaults tomailto:openclaw@localhost)
push.web.vapidPublicKey— fetches the active VAPID public key.push.web.subscribe— registers anendpointpluskeys.p256dh/keys.auth.push.web.unsubscribe— removes a registered endpoint.push.web.test— sends a test notification to the caller’s subscription.
Web Push is independent of the iOS APNS relay path (see Configuration for relay-backed push) and the existing
push.test method, which target native mobile pairing.Hosted embeds
Assistant messages can render hosted web content inline with the[embed ...] shortcode. The iframe sandbox policy is controlled by gateway.controlUi.embedSandbox:
- strict
- scripts (default)
- trusted
Disables script execution inside hosted embeds.
http(s) embed URLs stay blocked by default. If you intentionally want [embed url="https://..."] to load third-party pages, set gateway.controlUi.allowExternalEmbedUrls: true.
Tailnet access (recommended)
- Integrated Tailscale Serve (preferred)
- Bind to tailnet + token
Keep the Gateway on loopback and let Tailscale Serve proxy it with HTTPS:Open:
https://<magicdns>/(or your configuredgateway.controlUi.basePath)
tailscale-user-login) when gateway.auth.allowTailscale is true. OpenClaw verifies the identity by resolving the x-forwarded-for address with tailscale whois and matching it to the header, and only accepts these when the request hits loopback with Tailscale’s x-forwarded-* headers. For Control UI operator sessions with browser device identity, this verified Serve path also skips the device-pairing round trip; device-less browsers and node-role connections still follow the normal device checks. Set gateway.auth.allowTailscale: false if you want to require explicit shared-secret credentials even for Serve traffic. Then use gateway.auth.mode: "token" or "password".For that async Serve identity path, failed auth attempts for the same client IP and auth scope are serialized before rate-limit writes. Concurrent bad retries from the same browser can therefore show retry later on the second request instead of two plain mismatches racing in parallel.Insecure HTTP
If you open the dashboard over plain HTTP (http://<lan-ip> or http://<tailscale-ip>), the browser runs in a non-secure context and blocks WebCrypto. By default, OpenClaw blocks Control UI connections without device identity.
Documented exceptions:
- localhost-only insecure HTTP compatibility with
gateway.controlUi.allowInsecureAuth=true - successful operator Control UI auth through
gateway.auth.mode: "trusted-proxy" - break-glass
gateway.controlUi.dangerouslyDisableDeviceAuth=true
https://<magicdns>/(Serve)http://127.0.0.1:18789/(on the gateway host)
Insecure-auth toggle behavior
Insecure-auth toggle behavior
allowInsecureAuth is a local compatibility toggle only:- It allows localhost Control UI sessions to proceed without device identity in non-secure HTTP contexts.
- It does not bypass pairing checks.
- It does not relax remote (non-localhost) device identity requirements.
Break-glass only
Break-glass only
Trusted-proxy note
Trusted-proxy note
- Successful trusted-proxy auth can admit operator Control UI sessions without device identity.
- This does not extend to node-role Control UI sessions.
- Same-host loopback reverse proxies still do not satisfy trusted-proxy auth; see Trusted proxy auth.
Content security policy
The Control UI ships with a tightimg-src policy: only same-origin assets, data: URLs, and locally generated blob: URLs are allowed. Remote http(s) and protocol-relative image URLs are rejected by the browser and do not issue network fetches.
What this means in practice:
- Avatars and images served under relative paths (for example
/avatars/<id>) still render, including authenticated avatar routes that the UI fetches and converts into localblob:URLs. - Inline
data:image/...URLs still render (useful for in-protocol payloads). - Local
blob:URLs created by the Control UI still render. - Remote avatar URLs emitted by channel metadata are stripped at the Control UI’s avatar helpers and replaced with the built-in logo/badge, so a compromised or malicious channel cannot force arbitrary remote image fetches from an operator browser.
Avatar route auth
When gateway auth is configured, the Control UI avatar endpoint requires the same gateway token as the rest of the API:GET /avatar/<agentId>returns the avatar image only to authenticated callers.GET /avatar/<agentId>?meta=1returns the avatar metadata under the same rule.- Unauthenticated requests to either route are rejected (matching the sibling assistant-media route). This prevents the avatar route from leaking agent identity on hosts that are otherwise protected.
- The Control UI itself forwards the gateway token as a bearer header when fetching avatars, and uses authenticated blob URLs so the image still renders in dashboards.
Building the UI
The Gateway serves static files fromdist/control-ui. Build them with:
ws://127.0.0.1:18789).
Debugging/testing: dev server + remote Gateway
The Control UI is static files; the WebSocket target is configurable and can be different from the HTTP origin. This is handy when you want the Vite dev server locally but the Gateway runs elsewhere.Notes
Notes
gatewayUrlis stored in localStorage after load and removed from the URL.tokenshould be passed via the URL fragment (#token=...) whenever possible. Fragments are not sent to the server, which avoids request-log and Referer leakage. Legacy?token=query params are still imported once for compatibility, but only as a fallback, and are stripped immediately after bootstrap.passwordis kept in memory only.- When
gatewayUrlis set, the UI does not fall back to config or environment credentials. Providetoken(orpassword) explicitly. Missing explicit credentials is an error. - Use
wss://when the Gateway is behind TLS (Tailscale Serve, HTTPS proxy, etc.). gatewayUrlis only accepted in a top-level window (not embedded) to prevent clickjacking.- Non-loopback Control UI deployments must set
gateway.controlUi.allowedOriginsexplicitly (full origins). This includes remote dev setups. - Gateway startup may seed local origins such as
http://localhost:<port>andhttp://127.0.0.1:<port>from the effective runtime bind and port, but remote browser origins still need explicit entries. - Do not use
gateway.controlUi.allowedOrigins: ["*"]except for tightly controlled local testing. It means allow any browser origin, not “match whatever host I am using.” gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=trueenables Host-header origin fallback mode, but it is a dangerous security mode.
Related
- Dashboard — gateway dashboard
- Health Checks — gateway health monitoring
- TUI — terminal user interface
- WebChat — browser-based chat interface