Terminal
Last updated Sep 30, 2026 Note
This page documents Sandbox SDK 0.x for existing applications. For new applications, refer to Open a terminal in the browser. To move an existing application to @cloudflare/sandbox 1.0, refer to Migrate from Sandbox SDK 0.x.
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): void
Parameters:
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(): void
Properties
| 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
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/sdk/api/terminal/#page","headline":"Terminal","description":"Connect browser-based terminal UIs to Sandbox SDK 0.x shells over WebSocket.","url":"https://developers.cloudflare.com/sandbox/sdk/api/terminal/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/sdk/api/terminal/og.png?v=9c141078687cefae","dateModified":"2026-09-30","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Expose Sandbox SDK 0.x services on the public Internet with quick tunnels (*.trycloudflare.com) or named tunnels bound to a hostname on your Cloudflare zone. title: Tunnels image: https://developers.cloudflare.com/sandbox/sdk/api/tunnels/og.png?v=5d03fc562db367fd
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
Docs