@boxlite-ai/boxlite — not boxlite, not @boxlite/sdk — and it is ESM-only, so use import, never require.
What the Node.js SDK gives you
The BoxLite Node SDK runs untrusted code, crash-prone processes, and browser or desktop automation — all inside a lightweight microVM. Compared with containers, it provides VM-level isolation; compared with traditional VMs, it starts quickly and has a concise API. Typical scenarios:- Safely execute user-submitted code snippets on the server (AI-agent tool execution, online judges, data processing).
- Run a real Chromium/Firefox/WebKit for scraping or end-to-end tests, with the main process connecting remotely via Playwright.
- Give an AI a virtual desktop it can screenshot, click, and type into.
Prerequisites
- The
@boxlite-ai/boxliteNode package and a machine with hardware virtualization — see Installation.
Install:
The first run pulls the OCI image (such as alpine:latest) from the registry, which requires network access.
Quick Example
Minimal path: run one command withSimpleBox and clean up automatically afterward. Save the following as quickstart.mjs, then run node quickstart.mjs.
await using, which calls [Symbol.asyncDispose] automatically):
Parameters & Returns
Top-level exports
The runtime class isJsBoxlite; there is no bareBoxlite. In most cases you do not need to useJsBoxlitedirectly — the wrapper layers (SimpleBox, etc.) manage a default runtime internally.
SimpleBox — general command execution
new SimpleBox(options?: SimpleBoxOptions). The box is created lazily: construction does not start it; the first exec() pulls the image and starts the VM.
SimpleBoxOptions fields:
On thecpus/memoryMibdefaults: when unset, the SDK passesundefinedand the runtime applies 1 vCPU / 1024 MiB (vm_defaultsinsrc/boxlite/src/runtime/constants.rs). The exported constantsDEFAULT_CPUS/DEFAULT_MEMORY_MIBare not part of the creation path —DEFAULT_MEMORY_MIBin particular does not match the runtime value, so do not read it as the default. Production code should pass these values explicitly. See Compute resources.
SimpleBox methods:
The
ExecResult returned by exec:
exec with options (array form):
CodeBox — Python code sandbox
Inherits SimpleBox with a fixed image (CodeBoxOptions is Omit<SimpleBoxOptions, "image">, default image python:slim).
BrowserBox — browser automation
Inherits SimpleBox, default image mcr.microsoft.com/playwright:v1.58.0-jammy (bundles chromium/firefox/webkit, Playwright 1.58.0). BrowserBoxOptions extends Omit<SimpleBoxOptions, "image"|"cpus"|"memoryMib"> and adds:
The two connection modes are mutually exclusive (they share forwarded ports):
Prefer the Playwright Server mode (supports all browsers):
ComputerBox — desktop automation
Inherits SimpleBox, fixed image lscr.io/linuxserver/webtop:ubuntu-xfce. ComputerBoxOptions is Omit<SimpleBoxOptions, "image">, with dedicated defaults: cpus=2, memoryMib=2048, guiHttpPort=3000, guiHttpsPort=3001, display 1024x768.
Screenshot: { data: string /* base64 PNG */; width: number; height: number; format: "png" }.
InteractiveBox / SkillBox
InteractiveBox(PTY interactive terminal):InteractiveBoxOptions extends SimpleBoxOptions, and options are required at construction. Methods:start(),wait(),stop(),[Symbol.asyncDispose].SkillBox(runs AI CLIs such as Claude Code): default imageghcr.io/boxlite-ai/boxlite-skillbox:0.1.0, memory 4096, disk 10GB. Methods:start(),stop(),waitUntilReady(timeout?=60),call(prompt) => Promise<string>,installSkill(skillId) => Promise<boolean>. Requires an OAuth token (CLAUDE_CODE_OAUTH_TOKENor a constructor argument).
Security, network, volumes, ports, secrets
SimpleBoxOptions.security accepts a SecurityOptions object (on the Node side, pass an object directly; there are no preset static methods as in Python):
NetworkSpec: { mode: "enabled" | "disabled"; allowNet?: string[] }. mode:"enabled" with allowNet empty/omitted = all outbound allowed; with an array = an allowlist; mode:"disabled" removes the network interface.
Secret: { name: string; value: string; hosts?: string[]; placeholder?: string }. placeholder defaults to <BOXLITE_SECRET:${name}> and is replaced with value only in outbound HTTP(S) requests matching hosts.
Volume and port example (readOnly is a boolean):
JsBoxlite — runtime (when you need to manage multiple boxes)
List withlistInfo()(notlist()); remove withruntime.remove(idOrName, force?)(notbox.remove()).
JsBox / JsExecution (low-level handle and streaming)
JsBoxlite.create() / get() returns a JsBox, the low-level box handle. Its exec() returns a JsExecution, which exposes the raw stdin/stdout/stderr streams — useful for streaming long-running output or feeding interactive input. The high-level SimpleBox collects stdout/stderr into strings for you; use JsExecution directly only when you need streaming or stdin.
JsBox methods:
JsExecution methods:
JsExecStdin — writer for sending input to a running process:
JsExecStdout / JsExecStderr — readers for streaming output:
Each stream can only be consumed once. After iterating to EOF, subsequentnext()calls returnnull. Acquire each of stdout/stderr exactly once per execution.
JsExecResult (returned by wait()):
images — runtime image management
runtime.images is a runtime-scoped handle for cache operations. Both methods are async.
JsImagePullResult (from pull):
JsImageInfo (from list):
Remote BoxLite server (REST) and the routing prefix
Connect to a remote BoxLite server instead of the local runtime.JsBoxlite.rest takes a single BoxliteRestOptions bag — not (url, credential) positionally.
ApiKeyCredential structurally satisfies the exported Credential interface, so functions can type their parameter as Credential and accept any future credential kind without a signature change.
Routing prefix (vendor-agnostic). Box-scoped requests resolve to {url}/v1/{pathPrefix}/…. The v1 segment is hardcoded; pathPrefix is an opaque, deployment-defined routing value that the server tells the client to use, surfaced as Principal.path_prefix from GET /v1/me. Its semantics are vendor-specific: BoxLite cloud uses it for the organization ID; another deployment may use a workspace name, a region+team pair, or any other multi-segment value such as us-east/team-42.
pathPrefix is unset, the client builds URLs without the segment (/v1/boxes/…) — the canonical shape for single-tenant deployments such as the local boxlite serve reference server. (The CLI captures Principal.path_prefix at login and caches it under the active profile, so subsequent boxlite commands route correctly without an extra flag.)
Metric fields (note: named differently from Python/C/Go)
JsRuntimeMetrics:
JsBoxMetrics (partial):
JsBoxInfo / state
await box.info() returns JsBoxInfo: { id, name?, state: JsBoxStateInfo, createdAt, image, cpus, memoryMib, healthStatus }.
State lives incpus/memoryMib: the configured resource values. To read the real allocation, runnproc/ read/proc/meminfoinside the box.
JsBoxStateInfo: { status: string; running: boolean; pid?: number }. Correct access: box.info().state.status.
Troubleshooting
Package name / import error
@boxlite-ai/boxlite. Likewise, import { Boxlite } from '@boxlite/sdk' is entirely wrong (neither the package nor the class name exists) — the runtime class is JsBoxlite.
.mjs, or add "type": "module" to package.json; CommonJS projects should use a dynamic import().
Passing a string for a volume’s readOnly
If you carry over the CLI’s ro/rw syntax or write readOnly as a string, the native layer’s type conversion fails (a napi error, thrown as a bare Error):
Note: the Node-side error is the napi message above, not the Python-side TypeError: 'str' object cannot be cast as 'bool' (that one is the Python binding’s message).
Fix: readOnly is a boolean — { hostPath, guestPath, readOnly: true } (read-only) or false/omitted (read-write). ro/rw is only the CLI’s syntax (boxlite -v host:box:ro) and is unrelated to the SDK’s boolean field.
A non-zero exit code does not raise
exec reports a non-zero exit through exit_code / exitCode, not by raising. See Error Handling.
Command not found / startup failure throws a bare Error
When a command is not found, the thrown value is a bare Error (instanceof BoxliteError === false), with a message like internal error: spawn_failed: ...; image pull failures and missing virtualization also throw a standard Error. So you should not rely solely on catch (e) { if (e instanceof ExecError) ... } — that misses these cases. Use a general fallback:
ExecError/TimeoutError/ParseError are still thrown by some wrapper-layer APIs (for example, ComputerBox.cursorPosition throws ParseError on a parse failure) and can be handled specifically; but the low-level exec path mostly throws bare Error.
Accessing a sync getter before creation
box.id getter is unavailable before the first exec()/start. Use await box.getId() / await box.getInfo() instead, or run one command first. (box.info() is itself async — await it.)
Startup failure in a non-virtualization environment
On Linux without KVM, or on macOS Intel, the VM cannot start, andexec() throws (the process does not crash; the error can be caught).
- Linux: confirm
/dev/kvmexists and the current user is in thekvmgroup (ls -l /dev/kvm). - macOS: requires Apple Silicon (uses Hypervisor.framework, no KVM needed); Intel Macs are not supported.
- Windows: runs via WSL2 + KVM; native Windows is not supported.
BrowserBox connection needs playwright-core
playwright-core is an optional peerDependency; without it, import { chromium } from "playwright-core" fails. Only BrowserBox needs it: npm install playwright-core. Also, endpoint() (direct CDP) does not support WebKit; use playwrightEndpoint() for WebKit.
Cannot find package 'boxlite' / module not found
The package name has a scope. Both install and import must use @boxlite-ai/boxlite:

