Skip to main content
The package is @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/boxlite Node 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 with SimpleBox and clean up automatically afterward. Save the following as quickstart.mjs, then run node quickstart.mjs.
A more concise form (TypeScript 5.2+ / Node 20+ supports await using, which calls [Symbol.asyncDispose] automatically):

Parameters & Returns

Top-level exports

The runtime class is JsBoxlite; there is no bare Boxlite. In most cases you do not need to use JsBoxlite directly — 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 the cpus / memoryMib defaults: when unset, the SDK passes undefined and the runtime applies 1 vCPU / 1024 MiB (vm_defaults in src/boxlite/src/runtime/constants.rs). The exported constants DEFAULT_CPUS / DEFAULT_MEMORY_MIB are not part of the creation path — DEFAULT_MEMORY_MIB in 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 image ghcr.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_TOKEN or 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 with listInfo() (not list()); remove with runtime.remove(idOrName, force?) (not box.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, subsequent next() calls return null. Acquire each of stdout/stderr exactly once per execution.
JsExecResult (returned by wait()):
Writing to stdin:

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.
When 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 }.
cpus / memoryMib: the configured resource values. To read the real allocation, run nproc / read /proc/meminfo inside the box.
State lives in JsBoxStateInfo: { status: string; running: boolean; pid?: number }. Correct access: box.info().state.status.

Troubleshooting

Package name / import error

Cause: wrong package name. The correct name is @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.
Cause: the SDK is ESM-only. Rename the file to .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

Cause: the box is created lazily, so the sync 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, and exec() throws (the process does not crash; the error can be caught).
  • Linux: confirm /dev/kvm exists and the current user is in the kvm group (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:

See Also