Control UI (browser)
The Control UI is a small Vite + Lit single-page app served by the Gateway:- 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.passwordThe dashboard settings panel lets you store a token; passwords are not persisted. The onboarding wizard generates a gateway token by default, so paste it here on first connect.
Device pairing (first connection)
When you connect to the Control UI from a new browser or device, the Gateway requires a one-time pairing approval — even if you’re on the same Tailnet withgateway.auth.allowTailscale: true. This is a security measure to prevent
unauthorized access.
What you’ll see: “disconnected (1008): pairing required”
To approve the device:
openclaw devices revoke --device <id> --role <role>. See
Devices CLI for token rotation and revocation.
Notes:
- Local connections (
127.0.0.1) are auto-approved. - Remote connections (LAN, Tailnet, etc.) require explicit approval.
- Each browser profile generates a unique device ID, so switching browsers or clearing browser data will require re-pairing.
What it can do (today)
- Chat with the model via Gateway WS (
chat.history,chat.send,chat.abort,chat.inject) - Stream tool calls + live tool output cards in Chat (agent events)
- Channels: WhatsApp/Telegram/Discord/Slack + plugin channels (Mattermost, etc.) status + QR login + per-channel config (
channels.status,web.login.*,config.patch) - Instances: presence list + refresh (
system-presence) - Sessions: list + per-session thinking/verbose overrides (
sessions.list,sessions.patch) - Cron jobs: list/add/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: view/edit
~/.openclaw/openclaw.json(config.get,config.set) - Config: apply + restart with validation (
config.apply) and wake the last active session - Config writes include a base-hash guard to prevent clobbering concurrent edits
- Config schema + form rendering (
config.schema, including plugin + channel schemas); Raw JSON editor remains available - 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
- 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.
- New job form includes a Notify webhook toggle (
notifyon the job). - Gateway webhook posting requires both
notify: trueon the job andcron.webhookin config. - Set
cron.webhookTokento send a dedicated bearer token, if omitted the webhook is sent without an auth header.
Chat behavior
chat.sendis non-blocking: it acks immediately with{ runId, status: "started" }and the response streams viachatevents.- Re-sending with the same
idempotencyKeyreturns{ status: "in_flight" }while running, and{ status: "ok" }after completion. chat.injectappends an assistant note to the session transcript and broadcasts achatevent for UI-only updates (no agent run, no channel delivery).- Stop:
- Click Stop (calls
chat.abort) - Type
/stop(orstop|esc|abort|wait|exit|interrupt) to abort out-of-band chat.abortsupports{ sessionKey }(norunId) to abort all active runs for that session
- Click Stop (calls
- 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
Tailnet access (recommended)
Integrated Tailscale Serve (preferred)
Keep the Gateway on loopback and let Tailscale Serve proxy it with HTTPS: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. Set
gateway.auth.allowTailscale: false (or force gateway.auth.mode: "password")
if you want to require a token/password even for Serve traffic.
Bind to tailnet + token
http://<tailscale-ip>:18789/(or your configuredgateway.controlUi.basePath)
connect.params.auth.token).
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.
Recommended fix: use HTTPS (Tailscale Serve) or open the UI locally:
https://<magicdns>/(Serve)http://127.0.0.1:18789/(on the gateway host)
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.- Start the UI dev server:
pnpm ui:dev - Open a URL like:
gatewayUrlis stored in localStorage after load and removed from the URL.tokenis stored in localStorage;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.- For cross-origin dev setups (e.g.
pnpm ui:devto a remote Gateway), add the UI origin togateway.controlUi.allowedOrigins.