Terminal
Connect browser-based terminal UIs to sandbox shells via WebSocket. The server-side terminal() method proxies WebSocket connections to the container, and the client-side SandboxAddon integrates with xterm.js for terminal rendering.
Proxy a WebSocket upgrade request to create a terminal connection.
const response = await sandbox.terminal(request: Request, options?: PtyOptions): Promise<Response>Parameters:
request- WebSocket upgrade request from the browser (must includeUpgrade: websocketheader)options(optional):cols- Terminal width in columns (default:80)rows- Terminal height in rows (default:24)
Returns: Promise<Response> — WebSocket upgrade response
// In your Worker's fetch handlerreturn await sandbox.terminal(request, { cols: 120, rows: 30 });// In your Worker's fetch handlerreturn await sandbox.terminal(request, { cols: 120, rows: 30 });Works with both default and explicitly created sessions:
// Default sessionreturn await sandbox.terminal(request);
// Specific sessionconst session = await sandbox.getSession("dev");return await session.terminal(request);// Default sessionreturn await sandbox.terminal(request);
// Specific sessionconst session = await sandbox.getSession('dev');return await session.terminal(request);The @cloudflare/sandbox/xterm module provides SandboxAddon for xterm.js, which handles the WebSocket connection, reconnection, and terminal resize forwarding.
import { SandboxAddon } from '@cloudflare/sandbox/xterm';
const addon = new SandboxAddon(options: SandboxAddonOptions);Options:
getWebSocketUrl(params)- Build the WebSocket URL for each connection attempt. Receives:sandboxId- Target sandbox IDsessionId(optional) - Target session IDorigin- WebSocket origin derived fromwindow.location(for example,wss://example.com)
reconnect- Enable automatic reconnection with exponential backoff (default:true)onStateChange(state, error?)- Callback for connection state changes
import { Terminal } from "@xterm/xterm";import { SandboxAddon } from "@cloudflare/sandbox/xterm";
const terminal = new Terminal({ cursorBlink: true });terminal.open(document.getElementById("terminal"));
const addon = new SandboxAddon({ getWebSocketUrl: ({ sandboxId, sessionId, origin }) => { const params = new URLSearchParams({ id: sandboxId }); if (sessionId) params.set("session", sessionId); return `${origin}/ws/terminal?${params}`; }, onStateChange: (state, error) => { console.log(`Terminal ${state}`, error); },});
terminal.loadAddon(addon);addon.connect({ sandboxId: "my-sandbox" });import { Terminal } from '@xterm/xterm';import { SandboxAddon } from '@cloudflare/sandbox/xterm';
const terminal = new Terminal({ cursorBlink: true });terminal.open(document.getElementById('terminal'));
const addon = new SandboxAddon({ getWebSocketUrl: ({ sandboxId, sessionId, origin }) => { const params = new URLSearchParams({ id: sandboxId }); if (sessionId) params.set('session', sessionId); return `${origin}/ws/terminal?${params}`; }, onStateChange: (state, error) => { console.log(`Terminal ${state}`, error); }});
terminal.loadAddon(addon);addon.connect({ sandboxId: 'my-sandbox' });Establish a connection to a sandbox terminal.
addon.connect(target: ConnectionTarget): voidParameters:
target:sandboxId- Sandbox to connect tosessionId(optional) - Session within the sandbox
Calling connect() with a new target disconnects from the current target and connects to the new one. Calling it with the same target while already connected is a no-op.
Close the connection and stop any reconnection attempts.
addon.disconnect(): void| Property | Type | Description |
|---|---|---|
state | 'disconnected' | 'connecting' | 'connected' | Current connection state |
sandboxId | string | undefined | Current sandbox ID |
sessionId | string | undefined | Current session ID |
The SandboxAddon handles the WebSocket protocol automatically. These details are for building custom terminal clients without the addon. For a complete example, refer to Connect without xterm.js.
- Client opens a WebSocket to your Worker endpoint. Set
binaryTypetoarraybuffer. - The server replays any buffered output from a previous connection as binary frames. This may arrive before the
readymessage. - The server sends a
readystatus message — the terminal is now accepting input. - Binary frames flow in both directions: UTF-8 encoded keystrokes from the client, terminal output (including ANSI escape sequences) from the server.
- If the client disconnects, the PTY stays alive. Reconnecting to the same session replays buffered output so the terminal appears unchanged.
Send JSON text frames to control the terminal.
Resize — update terminal dimensions (both cols and rows must be positive):
{ "type": "resize", "cols": 120, "rows": 30 }The server sends JSON text frames for lifecycle events.
Ready — the PTY is initialized. Buffered output (if any) has already been sent:
{ "type": "ready" }Exit — the shell process has terminated:
{ "type": "exit", "code": 0, "signal": "SIGTERM" }Error — an error occurred (for example, invalid control message or session not found):
{ "type": "error", "message": "Session not found" }interface PtyOptions { cols?: number; rows?: number;}
type ConnectionState = "disconnected" | "connecting" | "connected";
interface ConnectionTarget { sandboxId: string; sessionId?: string;}
interface SandboxAddonOptions { getWebSocketUrl: (params: { sandboxId: string; sessionId?: string; origin: string; }) => string; reconnect?: boolean; onStateChange?: (state: ConnectionState, error?: Error) => void;}- Terminal connections — How terminal connections work
- Browser terminals — Step-by-step setup guide
- Sessions API — Session management
- Commands API — Non-interactive command execution