A terminal is an interactive PTY in the current container for a sandbox. Use it for full-duplex terminal I/O: a browser shell, resize, interrupt, and reconnect.
Command execution uses exec and process handles. Terminals are a separate resource type. API reference: Terminals API.
Process (exec) |
Terminal | |
|---|---|---|
| Role | Supervised argv process | Interactive PTY |
| Input | Launch-time argv (and whatever the program reads on its own) | PTY input via write() or browser connect() |
| Output | logs(), output(), waits |
output(), snapshot, waitForExit() |
| Stop | kill(signal?) |
interrupt() / terminate() |
| Lookup | getProcess / listProcesses |
getTerminal / listTerminals |
Both kinds of resource live only in the current container for a sandbox ID. Lookup methods do not start a container. Refer to Sandbox lifecycle and How long a process lives.
const terminal = await sandbox.createTerminal({
command: ["bash"],
cwd: "/workspace",
cols: 120,
rows: 40,
});
console.log(terminal.id);const terminal = await sandbox.createTerminal({
command: ["bash"],
cwd: "/workspace",
cols: 120,
rows: 40,
});
console.log(terminal.id);You can write to the PTY from the Worker, resize it, stream output, or end it:
await terminal.write(new TextEncoder().encode("uname -a\n"));
await terminal.resize(100, 30);
await terminal.terminate();await terminal.write(new TextEncoder().encode("uname -a\n"));
await terminal.resize(100, 30);
await terminal.terminate();- A terminal exists only in the current container for that sandbox ID.
getTerminal/listTerminalsreturnnull/[]when no container is running. They do not start one.- After the container stops or is replaced, old terminal IDs are invalid. Create a new terminal if you need one again.
- An active terminal can keep the container alive across Worker requests, as an active process can.
Store terminal.id to resume the same PTY while that container is still up.
- Create a terminal and keep
terminal.idwith the sandbox id. - On each WebSocket upgrade, resolve the terminal with
getTerminal, then returnterminal.connect(request). - In the browser, use
@cloudflare/sandbox/xtermwithterminalId.
import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (
url.pathname === "/ws/terminal" &&
request.headers.get("Upgrade")?.toLowerCase() === "websocket"
) {
const sandboxId = url.searchParams.get("sandboxId");
const terminalId = url.searchParams.get("terminalId");
if (!sandboxId || !terminalId) {
return new Response("sandboxId and terminalId are required", {
status: 400,
});
}
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.getTerminal(terminalId);
if (!terminal) {
return new Response("Terminal not found", { status: 404 });
}
return terminal.connect(request, {
cursor: url.searchParams.get("cursor") ?? undefined,
});
}
return new Response("Not found", { status: 404 });
},
};import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (
url.pathname === "/ws/terminal" &&
request.headers.get("Upgrade")?.toLowerCase() === "websocket"
) {
const sandboxId = url.searchParams.get("sandboxId");
const terminalId = url.searchParams.get("terminalId");
if (!sandboxId || !terminalId) {
return new Response("sandboxId and terminalId are required", {
status: 400,
});
}
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.getTerminal(terminalId);
if (!terminal) {
return new Response("Terminal not found", { status: 404 });
}
return terminal.connect(request, {
cursor: url.searchParams.get("cursor") ?? undefined,
});
}
return new Response("Not found", { status: 404 });
},
};Create the terminal from an application route when the UI needs one:
const sandboxId = "user-123";
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.createTerminal({ command: ["bash"] });
return Response.json({ sandboxId, terminalId: terminal.id });const sandboxId = "user-123";
const sandbox = getSandbox(env.Sandbox, sandboxId);
const terminal = await sandbox.createTerminal({ command: ["bash"] });
return Response.json({ sandboxId, terminalId: terminal.id });npm install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextyarn install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextpnpm install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextbun install @xterm/xterm @xterm/addon-fit @cloudflare/sandbox@nextimport { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { SandboxAddon } from "@cloudflare/sandbox/xterm";
import "@xterm/xterm/css/xterm.css";
const term = new Terminal({ cursorBlink: true });
const fitAddon = new FitAddon();
const sandboxAddon = new SandboxAddon({
// `origin` is already a WebSocket origin (`wss://` or `ws://`).
getWebSocketUrl: ({ sandboxId, terminalId, cursor, origin }) => {
const params = new URLSearchParams({ sandboxId });
if (terminalId) params.set("terminalId", terminalId);
if (cursor) params.set("cursor", cursor);
return `${origin}/ws/terminal?${params}`;
},
reconnect: true,
});
term.loadAddon(fitAddon);
term.loadAddon(sandboxAddon);
term.open(document.getElementById("terminal"));
fitAddon.fit();
// Values returned by your create-terminal route
const sandboxId = "user-123";
const terminalId = "term_...";
sandboxAddon.connect({ sandboxId, terminalId });import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { SandboxAddon } from "@cloudflare/sandbox/xterm";
import "@xterm/xterm/css/xterm.css";
const term = new Terminal({ cursorBlink: true });
const fitAddon = new FitAddon();
const sandboxAddon = new SandboxAddon({
// `origin` is already a WebSocket origin (`wss://` or `ws://`).
getWebSocketUrl: ({ sandboxId, terminalId, cursor, origin }) => {
const params = new URLSearchParams({ sandboxId });
if (terminalId) params.set("terminalId", terminalId);
if (cursor) params.set("cursor", cursor);
return `${origin}/ws/terminal?${params}`;
},
reconnect: true,
});
term.loadAddon(fitAddon);
term.loadAddon(sandboxAddon);
term.open(document.getElementById("terminal")!);
fitAddon.fit();
// Values returned by your create-terminal route
const sandboxId = "user-123";
const terminalId = "term_...";
sandboxAddon.connect({ sandboxId, terminalId });| Stable package | Preview |
|---|---|
sandbox.terminal(request) |
createTerminal + getTerminal + connect |
xterm / URL sessionId |
terminalId (and optional cursor) |