← Sandbox / sandbox / api
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.
Server-side methods
terminal()
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);Client-side addon
The @cloudflare/sandbox/xterm module provides SandboxAddon for xterm.js, which handles the WebSocket connection, reconnection, and terminal resize forwarding.
SandboxAddon
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' });connect()
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.
disconnect()
Close the connection and stop any reconnection attempts.
addon.disconnect(): voidProperties
| Property | Type | Description |
|---|---|---|
state |
'disconnected' | 'connecting' | 'connected' |
Current connection state |
sandboxId |
string | undefined |
Current sandbox ID |
sessionId |
string | undefined |
Current session ID |
WebSocket protocol
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.
Connection lifecycle
- 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.
Control messages (client to server)
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 }Status messages (server to client)
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" }Types
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;
}Related resources
- Terminal connections — How terminal connections work
- Browser terminals — Step-by-step setup guide
- Sessions API — Session management
- Commands API — Non-interactive command execution