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 handler
return await sandbox.terminal(request, { cols: 120, rows: 30 });// In your Worker's fetch handler
return await sandbox.terminal(request, { cols: 120, rows: 30 });Works with both default and explicitly created sessions:
// Default session
return await sandbox.terminal(request);
// Specific session
const session = await sandbox.getSession("dev");
return await session.terminal(request);// Default session
return await sandbox.terminal(request);
// Specific session
const 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