Skip to content

JS SDK Reference

Use the Vercel Sandbox JavaScript SDK to create isolated Linux microVMs on demand. Run untrusted code, test full Linux workflows, and manage files, ports, snapshots, drives, sessions, and network policy from Node.js applications.

For Python, see the Python SDK Reference.

Install the SDK:

Terminal
pnpm i @vercel/sandbox
Terminal
yarn add @vercel/sandbox
Terminal
npm i @vercel/sandbox
Terminal
bun add @vercel/sandbox

After installation, link your project and pull environment variables with vercel link and vercel env pull so the SDK can read a Vercel OpenID Connect (OIDC) token.

ClassWhat it doesExample
SandboxCreates and manages isolated microVM environmentsconst sandbox = await Sandbox.create()
SandboxUserRuns commands and file operations as a Linux userconst user = await sandbox.createUser('alice')
SessionRepresents a single running VM inside a sandboxconst session = sandbox.currentSession()
FileSystemProvides a node:fs/promises-compatible APIawait sandbox.fs.readFile('/tmp/a.txt')
CommandHandles running commands inside the sandboxconst cmd = await sandbox.runCommand()
CommandFinishedContains the result after a command completesAccess cmd.exitCode and cmd.stdout()
NetworkPolicyDefines firewall rules for sandbox trafficSandbox.create({ networkPolicy: 'deny-all' })
SnapshotRepresents a saved sandbox state for fast restartsconst snapshot = await sandbox.snapshot()
DriveRepresents persistent storage mounted to sandboxesconst drive = await Drive.getOrCreate()
// 1. Create a sandbox (persistent by default)
const sandbox = await Sandbox.create({ name: 'my-sandbox' });
 
// 2. Run a command - it waits for completion and returns the result
const result = await sandbox.runCommand('node', ['--version']);
 
// 3. Check the result
console.log(result.exitCode); // 0
console.log(await result.stdout()); // v22.x.x
 
// 4. Later, resume the same sandbox by name
const same = await Sandbox.get({ name: 'my-sandbox' });

The Sandbox class gives you full control over isolated Linux microVMs. Use it to create new sandboxes, retrieve existing ones, stream command output, snapshot the filesystem, and shut sessions down once your workflow is complete.

Sandboxes are persistent by default: when a sandbox stops, the SDK snapshots its filesystem and restores it on the next resume. See Persistent sandboxes for the full lifecycle model.

The name accessor returns the sandbox's unique name within the project. Use this string with Sandbox.get() to retrieve the sandbox in a later process. If you didn't pass a name to Sandbox.create(), one is generated for you.

Returns: string.

console.log(sandbox.name);

Whether the sandbox automatically snapshots its filesystem on stop. Defaults to true for sandboxes created with Sandbox.create().

Returns: boolean.

console.log(sandbox.persistent);

The status accessor reports the lifecycle state of the current session. Poll this value when you need to wait for startup or confirm shutdown, and treat failed as a signal to investigate.

Returns: "pending" | "running" | "stopping" | "stopped" | "failed".

console.log(sandbox.status);

timeout is the session timeout the sandbox was created with, in milliseconds. It stays constant for the sandbox's lifetime and never decreases while the session runs. Calling sandbox.extendTimeout() extends the live deadline but leaves timeout unchanged. To read how much time the session has left, use expiresAt.

Returns: number.

console.log(sandbox.timeout);

expiresAt returns the time when the current session will stop automatically. It works with sandbox.extendTimeout() and moves forward when you extend the timeout. For milliseconds remaining, subtract the current time.

Returns: Date | undefined.

const msRemaining = sandbox.expiresAt
  ? sandbox.expiresAt.getTime() - Date.now()
  : undefined;

Key-value tags associated with the sandbox. See Tags.

Returns: Record<string, string> | undefined.

console.log(sandbox.tags);

vcpus returns the number of allocated virtual CPUs. memory returns the allocated memory in MB (2048 MB per vCPU).

Returns: number | undefined.

console.log(sandbox.vcpus, sandbox.memory);

Deprecated: use image for image-based sandboxes.

The runtime image used by the sandbox (for example, "node24").

Returns: string | undefined.

console.log(sandbox.runtime);

The image used by the sandbox (for example, "vercel/sandbox/universal").

Returns: string | undefined.

console.log(sandbox.image);

The region where the sandbox runs. Set with the region parameter of Sandbox.create(). When no region was passed, this is the project's default sandbox region, or iad1 if the project doesn't set one.

Returns: string.

console.log(sandbox.region);

The additional regions this sandbox can fail over to. Returns an empty array when the sandbox has no failover regions.

Returns: string[].

console.log(sandbox.failoverRegions);

Timestamps for when the sandbox was created and last updated.

Returns: Date.

console.log(sandbox.createdAt, sandbox.updatedAt);

The ID of the snapshot the sandbox would resume from. Updated automatically when a persistent sandbox stops.

Returns: string | undefined.

console.log(sandbox.currentSnapshotId);

Default expiration (in milliseconds) applied to snapshots automatically created for this sandbox. 0 means no expiration. If undefined, snapshots expire after 30 days (2,592,000,000 ms).

Returns: number | undefined.

console.log(sandbox.snapshotExpiration);

Retention policy that keeps only the N most recent snapshots of this sandbox. A new snapshot is created whenever a persistent session stops or when you call sandbox.snapshot().

When a new snapshot brings the total above count for a given named sandbox, the oldest snapshots are no longer kept.

The deleteEvicted field controls what happens to them: delete them immediately, or leave them to expire on their own.

Returns: { count: number; expiration?: number; deleteEvicted?: boolean } | undefined. Returns undefined when no retention policy is set.

The policy object has the following fields:

FieldTypeRequiredDetails
countnumberYesNumber of most recent snapshots to keep. Must be an integer from 1 to 10. When a new snapshot brings the total above count, the oldest snapshots are no longer kept.
expirationnumberNoExpiration in milliseconds applied to the kept snapshots. Use 0 for no expiration. Falls back to snapshotExpiration when omitted.
deleteEvictedbooleanNoWhat happens to a snapshot once it is no longer among the count most recent. When true (the default), the snapshot is deleted immediately. When false, it is not deleted and remains until its existing expiration.
console.log(sandbox.keepLastSnapshots);

The amount of CPU used by the current session, in milliseconds. Only populated after the VM stops. Use this to track billable CPU for the latest session.

Returns: number | undefined.

console.log(sandbox.activeCpuUsageMs);

The amount of network data used by the current session, in bytes. Only populated after the VM stops.

Returns: { ingress: number; egress: number } | undefined.

console.log(sandbox.networkTransfer);

Cumulative usage across every session this sandbox has run. Use these for long-running, persistent sandboxes where the most recent session does not represent total usage. Network ingress and egress are usage metrics, not billing totals. See Pricing and Limits to understand which traffic is billable.

Returns: number | undefined.

console.log(sandbox.totalDurationMs, sandbox.totalActiveCpuDurationMs);
console.log(sandbox.totalIngressBytes, sandbox.totalEgressBytes);

The mounts accessor returns the drives mounted on the sandbox, keyed by mount path.

Returns: SandboxMounts.

console.log(sandbox.mounts);

Use Sandbox.list() to enumerate sandboxes for a project, with optional filters and cursor-based pagination. The returned value is async-iterable: iterate it directly to auto-paginate through every page, iterate result.pages() to walk page by page, or call result.toArray() to collect everything.

Returns: Promise<Paginated<{ sandboxes: Sandbox[]; pagination: Pagination; }>>.

ParameterTypeRequiredDetails
namePrefixstringNoFilter sandboxes whose name begins with this prefix. Requires sortBy: "name".
tagsRecord<string, string>NoFilter by a single tag.
sortBy"createdAt" | "name" | "statusUpdatedAt"NoSort field. Defaults to "createdAt".
sortOrder"asc" | "desc"NoSort direction. Defaults to "desc".
limitnumberNoMaximum number of sandboxes per page.
cursorstringNoPagination cursor returned by the previous page.
projectIdstringNoProject whose sandboxes you want to list.
signalAbortSignalNoCancel the request.
const result = await Sandbox.list({
  namePrefix: 'ci-',
  sortBy: 'name',
  tags: { env: 'staging' },
});
 
// Per-item iteration auto-paginates
for await (const sandbox of result) {
  console.log(sandbox.name);
}

Sandbox.create() launches a new microVM with your chosen image, source, and resource settings. Defaults to an empty workspace when no source is provided. Pass source.depth when cloning large repositories to shorten setup time.

Sandboxes are persistent by default: when the sandbox stops, the filesystem is automatically snapshotted and restored on the next resume. Pass persistent: false to opt out.

Returns: Promise<Sandbox>.

ParameterTypeRequiredDetails
namestringNoUnique sandbox name within the project. A random name is generated if omitted. Cannot be changed after creation.
sourcegitNoClone a Git repository.
url: string
username: string
password: string
depth?: number
revision?: string
sourcetarballNoMount a tarball.
url: string
sourcesnapshotNoCreate from a snapshot.
snapshotId: string
resources.vcpusnumberNoNumber of vCPUs (2048 MB RAM per vCPU). Defaults to 2.
runtimestringNoDeprecated: use image for image-based sandboxes. Runtime image such as "node26", "node24", "node22", or "python3.13".
imagestringNoVCR image reference, either a Vercel Managed Image or custom image. Defaults to "vercel/sandbox/universal".
portsnumber[]NoPorts to expose for sandbox.domain(). Up to 15.
timeoutnumberNoSession timeout in milliseconds. Defaults to 5 minutes.
regionstringNoRegion to create the sandbox in. Defaults to the project's default sandbox region, or iad1.
failoverRegionsstring[]NoAdditional regions the sandbox can fall back to when the main region is unavailable. Must not include the main region. Not supported together with mounts. Available on Pro and Enterprise plans, excluding Pro trials.
networkPolicyNetworkPolicyNoFirewall rules for sandbox egress traffic. Defaults to "allow-all".
envRecord<string, string>NoDefault environment variables for commands run in this sandbox. Per-command runCommand({ env }) values override these defaults.
mountsSandboxMountsNoDrives to attach to the sandbox, keyed by absolute mount path. Drives can be mounted as "read-write" (default) or "read-only".
tagsRecord<string, string>NoUp to five key-value tags.
persistentbooleanNoAuto-snapshot the filesystem on stop and restore on resume. Defaults to true.
snapshotExpirationnumberNoDefault snapshot TTL in milliseconds. Defaults to 30 days (2,592,000,000 ms). Use 0 for no expiration.
keepLastSnapshotsobjectNoRetention policy that keeps only the N most recent snapshots. { count: 1-10, expiration?: number, deleteEvicted?: boolean }. See keepLastSnapshots for field details.
onResume(sandbox) => Promise<void>NoFires whenever a session resumes, including after auto-resume. Use to restart background services or rehydrate caches.
signalAbortSignalNoCancel sandbox creation.

For VCR repository setup and push commands, see Getting Started with Vercel Container Registry. For how custom images work in Sandbox, see Images.

const sandbox = await Sandbox.create({
  name: 'my-sandbox',
  image: 'vercel/sandbox/node:26',
  networkPolicy: 'deny-all',
  env: { NODE_ENV: 'production' },
  tags: { env: 'staging' },
  snapshotExpiration: 7 * 24 * 60 * 60 * 1000, // 7 days
});

Sandbox.get() retrieves an existing sandbox by name. If the sandbox is stopped, the next SDK call (such as runCommand) automatically resumes it. Pass resume: false to skip the implicit resume.

Returns: Promise<Sandbox>.

ParameterTypeRequiredDetails
namestringYesSandbox name to retrieve.
resumebooleanNoWhether to resume the sandbox immediately. Defaults to true.
onResume(sandbox) => Promise<void>NoFires whenever a session resumes (including after auto-resume).
signalAbortSignalNoCancel the request.
const sandbox = await Sandbox.get({ name: 'my-sandbox' });

Sandbox.getOrCreate() is the recommended pattern for long-lived sandboxes: it resumes the sandbox if it exists or creates it if it doesn't. Use onCreate for one-time setup and onResume for setup that should run on every session start.

Creation parameters (such as keepLastSnapshots or snapshotExpiration) apply only when getOrCreate creates the sandbox. If a sandbox with the same name already exists, getOrCreate returns it with its existing configuration and ignores the creation parameters you pass. To change the configuration of an existing sandbox, call sandbox.update().

Returns: Promise<Sandbox>.

ParameterTypeRequiredDetails
namestringNoSandbox name. If omitted, behaves like Sandbox.create (always creates, onCreate always fires).
resumebooleanNoWhether to resume the sandbox immediately when an existing one is retrieved. Defaults to false. When true, the sandbox is resumed before getOrCreate resolves, and onResume is awaited before it resolves.
onCreate(sandbox) => Promise<void>NoRuns once when the sandbox is freshly created. Awaited before getOrCreate resolves.
onResume(sandbox) => Promise<void>NoRuns every time the session resumes (including after auto-resume). By default, getOrCreate retrieves the sandbox but does not resume it, so onResume is not awaited before it resolves; the session resumes on the first SDK call (such as runCommand), and onResume runs at that point. Pass resume: true to resume immediately and have onResume awaited before getOrCreate resolves.
...all Sandbox.create paramsNoAny creation parameter is accepted and used only when the sandbox is created. Ignored when an existing sandbox is retrieved.

Behavior:

  • If a sandbox with that name exists, getOrCreate retrieves it without resuming it by default. The sandbox resumes on the first SDK call (such as runCommand), and onResume fires at that point — not before getOrCreate resolves. Pass resume: true to resume immediately and have onResume awaited before getOrCreate resolves.
  • If a sandbox with that name exists, its configuration is not updated. Creation parameters passed to getOrCreate are ignored; use sandbox.update() to change the configuration of an existing sandbox.
  • If no sandbox exists, a fresh sandbox is created with the parameters you pass, and onCreate is awaited before getOrCreate resolves.
  • If the sandbox exists but its snapshot expired, the stale sandbox is deleted, re-created with the same name, and onCreate fires.
const sandbox = await Sandbox.getOrCreate({
  name: 'my-sandbox',
  onCreate: async (sbx) => {
    await sbx.runCommand('git', ['clone', repoUrl, '.']);
    await sbx.runCommand('npm', ['install']);
  },
  onResume: async (sbx) => {
    await sbx.runCommand({ cmd: 'npm', args: ['run', 'dev'], detached: true });
  },
});

Sandbox.fork() creates a new sandbox seeded from the current snapshot of an existing one. The new sandbox inherits the source's config, including its environment variables. Any field you pass in overrides the copied value. If the source has no current snapshot, the fork falls back to a fresh create with the source's image plus the copied config.

Pass env to override the copied environment variables. image is not accepted as an override — when the source has a snapshot the image is inherited from it; otherwise it is copied from the source sandbox.

The fork runs in the source sandbox's region unless you pass region. The fork also inherits the source's failover regions; pass failoverRegions to replace them. If the source has a snapshot, that snapshot must be available in the target region.

Returns: Promise<Sandbox>.

ParameterTypeRequiredDetails
sourceSandboxstringYesName of the sandbox to fork from.
...most Sandbox.create paramsNoAny field set here overrides the value copied from the source. source and image are not accepted.
// Inherit every supported field from the source
const fork = await Sandbox.fork({ sourceSandbox: 'prod-agent' });
 
// Override specific fields; the rest are copied from the source
const customized = await Sandbox.fork({
  sourceSandbox: 'prod-agent',
  name: 'forked-prod-agent',
  resources: { vcpus: 4 },
  env: { OPENAI_API_KEY: process.env.OPENAI_API_KEY! },
});

Call sandbox.getCommand() to retrieve a previously executed command by its ID, which is especially helpful after detached executions when you want to inspect logs later.

Returns: Promise<Command>.

ParameterTypeRequiredDetails
cmdIdstringYesIdentifier of the command to fetch.
opts.signalAbortSignalNoCancel the lookup if it takes too long.
const command = await sandbox.getCommand(cmdId);

sandbox.runCommand() executes commands inside the microVM, either blocking until completion or returning immediately in detached mode. Use detached: true for long-running servers, stream output to local log handlers, and call command.wait() later for results.

Returns: Promise<CommandFinished> when detached is false; Promise<Command> when detached is true.

ParameterTypeRequiredDetails
commandstringYesCommand to execute (string overload).
argsstring[]NoArguments for the string overload.
opts.signalAbortSignalNoCancel the command (string overload).
params.cmdstringYesCommand to execute when using the object overload.
params.argsstring[]NoArguments for the object overload.
params.cwdstringNoWorking directory for execution.
params.envRecord<string, string>NoAdditional environment variables.
params.sudobooleanNoRun the command with sudo.
params.detachedbooleanNoReturn immediately with a live Command object.
params.stdoutWritableNoStream standard output to a writable.
params.stderrWritableNoStream standard error to a writable.
params.signalAbortSignalNoCancel the command when using the object overload.
const result = await sandbox.runCommand('node', ['--version']);

sandbox.mkDir() creates a directory in the sandbox filesystem before you write files or clone repositories. Paths are relative to /vercel/sandbox unless you provide an absolute path. Call this before writeFiles() when your target directory does not exist yet.

await sandbox.mkDir('assets');
ParameterTypeRequiredDetails
pathstringYesDirectory to create.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

Use sandbox.readFile() to pull file contents from the sandbox to a ReadableStream. The promise resolves to null when the file does not exist. You can use sandbox.readFileToBuffer() directly if you prefer receiving a Buffer.

const stream = await sandbox.readFile({ path: 'package.json' });
ParameterTypeRequiredDetails
file.pathstringYesPath to the file inside the sandbox.
file.cwdstringNoBase directory for resolving file.path.
opts.signalAbortSignalNoCancel the read operation.

Returns: Promise<null | ReadableStream>.

Use sandbox.readFileToBuffer() to pull entire file contents from the sandbox to an in-memory buffer. The promise resolves to null when the file does not exist.

const buffer = await sandbox.readFileToBuffer({ path: 'package.json' });
ParameterTypeRequiredDetails
file.pathstringYesPath to the file inside the sandbox.
file.cwdstringNoBase directory for resolving file.path.
opts.signalAbortSignalNoCancel the read operation.

Returns: Promise<null | Buffer>.

Use sandbox.downloadFile() to pull file contents from the sandbox to a local destination. The promise resolves to the absolute destination path or null when the source file does not exist.

const dstPath = await sandbox.downloadFile(
  { path: 'package.json', cwd: '/vercel/sandbox' },
  { path: 'local-package.json', cwd: '/tmp' }
);
ParameterTypeRequiredDetails
src.pathstringYesPath to the file inside the sandbox.
src.cwdstringNoBase directory for resolving src.path.
dst.pathstringYesPath to local destination.
dst.cwdstringNoBase directory for resolving dst.path.
opts.signalAbortSignalNoCancel the download operation.
opts.mkdirRecursivebooleanNoCreate destination directories recursively if they do not exist.

Returns: Promise<null | string>.

sandbox.writeFiles() uploads one or more files into the sandbox filesystem. Paths default to /vercel/sandbox; use absolute paths for custom locations and bundle related files into a single call to reduce round trips.

await sandbox.writeFiles([{ path: 'hello.txt', content: Buffer.from('hi') }]);

You can set Unix file permissions using the optional mode property. For example, use 0o755 for executable scripts:

await sandbox.writeFiles([
  { path: 'run.sh', content: Buffer.from('#!/bin/bash\necho "Hello"'), mode: 0o755 },
]);
ParameterTypeRequiredDetails
files{ path: string; content: Buffer; mode?: number; }[]YesFile descriptors to write.
files.pathstringYesPath to the file inside the sandbox.
files.contentBufferYesFile contents as a Buffer.
files.modenumberNoUnix file permissions in octal (for example, 0o644 for read/write, 0o755 for executable).
opts.signalAbortSignalNoCancel the write operation.

Returns: Promise<void>.

sandbox.domain() resolves a publicly accessible URL for a port you exposed during creation. It throws if the port is not registered to a route, so include the port in the ports array when creating the sandbox and cache the returned URL so you can share it quickly with collaborators.

const previewUrl = sandbox.domain(3000);
ParameterTypeRequiredDetails
pnumberYesPort number declared in ports.

Returns: string.

Returns the current Session for the sandbox. A session represents a single running VM instance. The SDK creates and resumes sessions for you; usually you don't need to interact with sessions directly.

const session = sandbox.currentSession();
console.log(session.sessionId, session.status);

Returns: Session.

sandbox.stop() resolves once the VM is fully stopped, and returns the final session state. For persistent sandboxes, the resolved value also includes metadata for the snapshot captured during shutdown. It's safe to call multiple times.

const result = await sandbox.stop();
console.log(result.snapshot?.id);
console.log(result.activeCpuDurationMs);
console.log(result.networkTransfer); // { ingress, egress }
ParameterTypeRequiredDetails
opts.signalAbortSignalNoCancel the stop operation.

Returns: Promise<{ snapshot?: { id: string; status: "created" | "deleted" | "failed"; sizeBytes: number; createdAt: number; expiresAt?: number; parentId?: string }; activeCpuDurationMs: number; networkTransfer: { ingress: number; egress: number } }>.

sandbox.update() updates any mutable parameter on the sandbox. When ports is provided, it is treated as the full desired port list: any currently exposed port not present in the array is deregistered. networkPolicy is applied to the current session as well as future sessions.

await sandbox.update({
  resources: { vcpus: 4 },
  timeout: 30 * 60 * 1000,
  networkPolicy: 'deny-all',
  ports: [3000, 8000],
  tags: { env: 'prod' },
  persistent: false,
  snapshotExpiration: 14 * 24 * 60 * 60 * 1000,
  keepLastSnapshots: { count: 1 },
  currentSnapshotId: 'snap_xyz', // Roll back to a previous snapshot
  failoverRegions: ['cle1'], // Replaces the list; pass [] to remove them
});
ParameterTypeRequiredDetails
resources.vcpusnumberNoNew vCPU count (memory auto-scales to 2048 MB per vCPU).
timeoutnumberNoNew session timeout in milliseconds.
networkPolicyNetworkPolicyNoReplace the firewall policy on the current and future sessions.
portsnumber[]NoReplace the exposed ports. Ports not present in the array are deregistered.
tagsRecord<string, string>NoReplace the tag set. Pass {} to clear.
persistentbooleanNoEnable or disable auto-snapshotting on stop.
snapshotExpirationnumberNoNew default snapshot TTL in milliseconds. Use 0 for no expiration.
keepLastSnapshotsobject | nullNoRetention policy that keeps only the N most recent snapshots. Pass null to clear. See keepLastSnapshots for field details.
currentSnapshotIdstringNoPoint the sandbox at a different snapshot. New sessions resume from it.
failoverRegionsstring[]NoReplace the failover regions. Must not include the sandbox's main region. Not supported for sandboxes with mounts. Pass [] to remove them. Applies to the next session; the running session keeps the region it started in. Available on Pro and Enterprise plans, excluding Pro trials.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

Permanently delete the sandbox and all of its sessions. Its snapshots stay available until they expire or you delete them with snapshot.delete(). After deletion the instance becomes inert: further API calls throw immediately.

await sandbox.delete();
ParameterTypeRequiredDetails
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

List the VM sessions that have been created for this sandbox. Returns an async-iterable that auto-paginates.

const sessions = await sandbox.listSessions();
for await (const session of sessions) {
  console.log(session.id, session.status);
}
ParameterTypeRequiredDetails
limitnumberNoMaximum number of sessions per page.
cursorstringNoPagination cursor.
sortOrder"asc" | "desc"NoSort direction. Defaults to "desc".
signalAbortSignalNoCancel the request.

Returns: Promise<Paginated<{ sessions: Session[]; pagination: Pagination; }>>.

List the snapshots that belong to this sandbox. Returns an async-iterable that auto-paginates.

const snapshots = await sandbox.listSnapshots();
for await (const snapshot of snapshots) {
  console.log(snapshot.id, snapshot.status);
}
ParameterTypeRequiredDetails
limitnumberNoMaximum number of snapshots per page.
cursorstringNoPagination cursor.
sortOrder"asc" | "desc"NoSort direction. Defaults to "desc".
signalAbortSignalNoCancel the request.

Returns: Promise<Paginated<{ snapshots: Snapshot[]; pagination: Pagination; }>>.

sandbox.updateNetworkPolicy() is deprecated. Use sandbox.update({ networkPolicy }) instead.

Update the firewall settings applied to the sandbox egress traffic. The provided configuration fully replaces the pre-existing one. This allows for instance a user to start a sandbox, gather data, then run some untrusted program on it without risking data exfiltration.

await sandbox.updateNetworkPolicy('allow-all'); // Allow all egress from the sandbox
 
await sandbox.updateNetworkPolicy('deny-all'); // Block all egress from the sandbox
 
await sandbox.updateNetworkPolicy({allow: ["google.com", "ai-gateway.vercel.sh"]}); // Allow traffic to specific websites only
 
// Allow traffic to specific websites and private network
await sandbox.updateNetworkPolicy({
  allow: ["google.com", "ai-gateway.vercel.sh"],
  subnets: {
    allow: ["10.0.0.0/8"],
  },
});
 
// Allow traffic to the Internet while blocking private network
await sandbox.updateNetworkPolicy({
  subnets: {
    deny: ["10.0.0.0/8"],
  },
});
 
// Allow traffic to a specific website with credential brokering
await sandbox.updateNetworkPolicy({
  allow: {
    "ai-gateway.vercel.sh": [{
      transform: [{
        headers: {
          "x-api-key": "secret-key"
        }
      }]
    }]
  }
});
ParameterTypeRequiredDetails
networkPolicyNetworkPolicyYesNew firewall setup. Will fully replace the existing one.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

Use sandbox.extendTimeout() to extend the sandbox lifetime by the specified duration. This lets you keep the sandbox running up to the maximum execution timeout for your plan, so check sandbox.timeout first and extend only when necessary to avoid premature shutdown.

await sandbox.extendTimeout(60000); // Extend by 60 seconds
ParameterTypeRequiredDetails
durationnumberYesDuration in milliseconds to extend the timeout by.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

Call sandbox.snapshot() to capture the current state of the sandbox, including the filesystem and installed packages. Use snapshots to skip lengthy setup steps when creating new sandboxes. To learn more, see Snapshots.

The sandbox must be running to create a snapshot. Once you call this method, the sandbox shuts down automatically and becomes unreachable. You do not need to call stop() afterwards, and any subsequent commands to the sandbox will fail.

Snapshots expire 30 days after their last use by default. Set expiration to 0 to disable expiration, or choose a custom duration in milliseconds (e.g., ms('14d')) to fit your workflow.

index.ts
const snapshot = await sandbox.snapshot({ expiration: ms('14d') });
console.log(snapshot.snapshotId);
 
// Later, create a new sandbox from the snapshot
const newSandbox = await Sandbox.create({
  source: { type: 'snapshot', snapshotId: snapshot.snapshotId },
});
ParameterTypeRequiredDetails
opts.expirationnumberNoOptional expiration time in milliseconds, measured from the snapshot's last use. Use 0 for no expiration at all.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<Snapshot>.

sandbox.createUser() adds a Linux user with an isolated home directory at /home/<username>. The user gets /bin/bash as their login shell, and their home directory is private to other users but readable by the SDK. Use this to give each agent in a multi-agent workflow its own workspace.

const alice = await sandbox.createUser('alice');
console.log(alice.username, alice.homeDir); // "alice" "/home/alice"
ParameterTypeRequiredDetails
usernamestringYesLinux username. Must match /^[a-z_][a-z0-9_-]*$/ and be at most 32 characters.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<SandboxUser>.

sandbox.asUser() returns a SandboxUser handle for a user that already exists, without creating it. Use it for users restored from a snapshot or created outside the SDK, such as root.

const bob = sandbox.asUser('bob');
ParameterTypeRequiredDetails
usernamestringYesLinux username. Must match /^[a-z_][a-z0-9_-]*$/ and be at most 32 characters.

Returns: SandboxUser.

sandbox.createGroup() creates a Linux group with a shared directory at /shared/<groupname>. The directory uses the setgid bit, so files created inside it inherit the group, and every member can read one another's files and add their own. Add members with sandbox.addUserToGroup().

const devs = await sandbox.createGroup('devs');
console.log(devs.sharedDir); // "/shared/devs"
ParameterTypeRequiredDetails
groupnamestringYesGroup name. Must match /^[a-z_][a-z0-9_-]*$/ and be at most 32 characters.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<{ groupname: string; sharedDir: string }>.

sandbox.addUserToGroup() adds a user to a group. Once added, the user can access the group's shared directory at /shared/<groupname>, where every member can read one another's files and add their own.

await sandbox.addUserToGroup('alice', 'devs');
ParameterTypeRequiredDetails
usernamestringYesThe user to add.
groupnamestringYesThe group to add the user to.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

sandbox.removeUserFromGroup() removes a user from a group, revoking their access to the group's shared directory. Revocation applies to new commands immediately. Processes already running as that user keep the group until they exit.

await sandbox.removeUserFromGroup('alice', 'devs');
ParameterTypeRequiredDetails
usernamestringYesThe user to remove.
groupnamestringYesThe group to remove the user from.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

A SandboxUser runs commands and file operations as a specific Linux user. Commands execute as that user, and file methods resolve relative paths against the user's home directory. Get an instance from sandbox.createUser() or sandbox.asUser(). See Multi-agent sandboxes for a task-oriented guide.

Multi-user support is available in the JS SDK (@vercel/sandbox) only, and the sandbox image must include /bin/bash.

The Linux username this instance runs as.

Returns: string.

The user's home directory. This is /home/<username> for users created with createUser(), and /root for the root user.

Returns: string.

const alice = await sandbox.createUser('alice');
console.log(alice.homeDir); // "/home/alice"

user.runCommand() runs a command as this user. It accepts the same arguments as sandbox.runCommand(), with two differences: the working directory defaults to the user's home directory, and passing sudo: true escalates that single command to root.

Returns: Promise<CommandFinished> when detached is false; Promise<Command> when detached is true.

ParameterTypeRequiredDetails
commandstringYesCommand to execute (string overload).
argsstring[]NoArguments for the string overload.
opts.signalAbortSignalNoCancel the command (string overload).
params.cmdstringYesCommand to execute when using the object overload.
params.argsstring[]NoArguments for the object overload.
params.cwdstringNoWorking directory. Defaults to the user's home directory.
params.envRecord<string, string>NoAdditional environment variables.
params.sudobooleanNoRun the command as root instead of this user.
params.detachedbooleanNoReturn immediately with a live Command object.
params.stdoutWritableNoStream standard output to a writable.
params.stderrWritableNoStream standard error to a writable.
params.signalAbortSignalNoCancel the command when using the object overload.
const whoami = await alice.runCommand('whoami');
console.log(await whoami.stdout()); // "alice\n"
 
// Object form with environment variables and a custom working directory
await alice.runCommand({
  cmd: 'node',
  args: ['index.js'],
  env: { API_KEY: 'your_api_key_here' },
  cwd: '/tmp',
});

user.writeFiles() writes files as this user. Relative paths resolve to the user's home directory, and the written files are owned by the user.

await alice.writeFiles([
  { path: 'app.js', content: Buffer.from('console.log("hi")') },
]);
ParameterTypeRequiredDetails
files{ path: string; content: string | Uint8Array; mode?: number; }[]YesFile descriptors to write. Relative paths resolve to the home directory.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

user.readFile() reads a file as this user and returns a stream, or null if the file does not exist. Relative paths resolve to the user's home directory.

const stream = await alice.readFile({ path: 'app.js' });
// stream is a Node.js ReadableStream, or null if the file does not exist.
// For a Buffer instead of a stream, use readFileToBuffer().
ParameterTypeRequiredDetails
file{ path: string; cwd?: string }YesFile to read. A relative path resolves to the home directory.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<NodeJS.ReadableStream | null>.

user.readFileToBuffer() reads a file as this user and returns a Buffer, or null if the file does not exist. Relative paths resolve to the user's home directory.

const buf = await alice.readFileToBuffer({ path: 'app.js' });
console.log(buf?.toString());
ParameterTypeRequiredDetails
file{ path: string; cwd?: string }YesFile to read. A relative path resolves to the home directory.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<Buffer | null>.

user.downloadFile() reads a file as this user and writes it to the local filesystem, returning the absolute local path, or null if the source file does not exist.

await alice.downloadFile({ path: 'app.js' }, { path: './app.js' });
ParameterTypeRequiredDetails
src{ path: string; cwd?: string }YesSource file in the sandbox. A relative path resolves to the home directory.
dst{ path: string; cwd?: string }YesDestination on the local machine.
opts.mkdirRecursivebooleanNoCreate parent directories on the local machine if they are missing.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<string | null>.

user.mkDir() creates a directory owned by this user. Relative paths resolve to the user's home directory.

await alice.mkDir('projects/my-app');
ParameterTypeRequiredDetails
pathstringYesDirectory to create. A relative path resolves to the home directory.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

user.addToGroup() adds this user to a group. It is a convenience wrapper around sandbox.addUserToGroup().

await alice.addToGroup('devs');
ParameterTypeRequiredDetails
groupnamestringYesThe group to join.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

user.removeFromGroup() removes this user from a group. It is a convenience wrapper around sandbox.removeUserFromGroup().

await alice.removeFromGroup('devs');
ParameterTypeRequiredDetails
groupnamestringYesThe group to leave.
opts.signalAbortSignalNoCancel the operation.

Returns: Promise<void>.

A Session represents a single running VM instance inside a sandbox. Sessions are created and resumed for you whenever the sandbox transitions from stopped to running. Use sandbox.currentSession() to inspect the active one, or sandbox.listSessions() to enumerate every session the sandbox has run.

Read-only accessors on a Session instance returned by sandbox.currentSession(). Iterating sandbox.listSessions() yields raw API objects with the same fields keyed as id instead of sessionId.

AccessorReturnsDescription
sessionIdstringUnique identifier of the session.
status"pending" | "running" | "stopping" | "stopped" | "failed"Current lifecycle state.
createdAtDateWhen the session started.
activeCpuUsageMsnumber | undefinedCPU used during the session, in milliseconds. Available once the session is stopped.
networkTransfer{ ingress: number; egress: number } | undefinedNetwork traffic during the session. Available once the session is stopped.
const session = sandbox.currentSession();
console.log(session.sessionId, session.status);

FileSystem gives you a node:fs/promises-compatible surface for sandbox files and directories. Use it through sandbox.fs to keep code portable when you already have utilities that expect familiar Node.js filesystem methods.

const sandbox = await Sandbox.create();
 
await sandbox.fs.writeFile('/tmp/hello.txt', 'hello from sandbox');
const text = await sandbox.fs.readFile('/tmp/hello.txt', 'utf8');
console.log(text);

FileSystem currently implements a focused subset of node:fs/promises for sandbox workflows. APIs such as file handles and watchers are not currently included.

All methods support AbortSignal through an options object. Methods throw Node.js-style filesystem errors with fields such as code, syscall, and path.

MethodSignature summaryReturns
sandbox.fs.readFile()path, optional encoding ('utf8' or { encoding, signal })Promise<Buffer | string>
sandbox.fs.writeFile()path, string | Buffer | Uint8Array, optional { encoding, signal }Promise<void>
sandbox.fs.appendFile()path, string | Buffer | Uint8Array, optional { encoding, signal }Promise<void>
await sandbox.fs.writeFile('/tmp/config.json', JSON.stringify({ mode: 'test' }));
const config = await sandbox.fs.readFile('/tmp/config.json', 'utf8');
await sandbox.fs.appendFile('/tmp/config.json', '\n');
MethodSignature summaryReturns
sandbox.fs.mkdir()path, optional { recursive, signal }Promise<string | undefined>
sandbox.fs.readdir()path, optional { withFileTypes, signal }Promise<string[] | Dirent[]>
sandbox.fs.stat()path, optional { signal }Promise<Stats>
sandbox.fs.lstat()path, optional { signal }Promise<Stats>
sandbox.fs.realpath()path, optional { signal }Promise<string>
sandbox.fs.mkdtemp()prefix, optional { signal }Promise<string>
await sandbox.fs.mkdir('/tmp/results', { recursive: true });
 
const entries = await sandbox.fs.readdir('/tmp', { withFileTypes: true });
for (const entry of entries) {
  if (entry.isDirectory()) console.log(`dir: ${entry.name}`);
}
 
const stats = await sandbox.fs.stat('/tmp/results');
console.log(stats.isDirectory()); // true
MethodSignature summaryReturns
sandbox.fs.unlink()path, optional { signal }Promise<void>
sandbox.fs.rm()path, optional { recursive, force, signal }Promise<void>
sandbox.fs.rmdir()path, optional { signal }Promise<void>
sandbox.fs.rename()oldPath, newPath, optional { signal }Promise<void>
sandbox.fs.copyFile()src, dest, optional { signal }Promise<void>
sandbox.fs.truncate()path, optional len, optional { signal }Promise<void>
await sandbox.fs.copyFile('/tmp/input.txt', '/tmp/output.txt');
await sandbox.fs.rename('/tmp/output.txt', '/tmp/final.txt');
await sandbox.fs.truncate('/tmp/final.txt', 1024);
await sandbox.fs.rm('/tmp/final.txt');
MethodSignature summaryReturns
sandbox.fs.chmod()path, mode (number | string), optional { signal }Promise<void>
sandbox.fs.chown()path, uid, gid, optional { signal }Promise<void>
sandbox.fs.symlink()target, path, optional { signal }Promise<void>
sandbox.fs.readlink()path, optional { signal }Promise<string>
await sandbox.fs.chmod('/tmp/script.sh', 0o755);
await sandbox.fs.symlink('/tmp/script.sh', '/tmp/current-script');
const linkTarget = await sandbox.fs.readlink('/tmp/current-script');
console.log(linkTarget);
MethodSignature summaryReturns
sandbox.fs.access()path, optional { signal }Promise<void>
sandbox.fs.exists()path, optional { signal }Promise<boolean>
await sandbox.fs.access('/tmp/config.json');
const hasCache = await sandbox.fs.exists('/tmp/cache.db');

sandbox.fs.exists() is a convenience helper that is not part of the Node.js node:fs/promises API. sandbox.fs.access() currently checks path existence.

Command instances represent processes that run inside a sandbox. Detached executions created through sandbox.runCommand({ detached: true, ... }) return a Command immediately so that you can stream logs or stop the process later. Blocking executions that do not set detached still expose these methods through the CommandFinished object they resolve to.

The exitCode property holds the process exit status once the command finishes. For detached commands, this value starts as null and gets populated after you await command.wait(), so check for null to determine if the command is still running.

if (command.exitCode !== null) {
  console.log(`Command exited with code: ${command.exitCode}`);
}

Returns: number | null.

The durationMs property measures how long the command took to execute in milliseconds. For detached commands, the value starts as undefined and gets populated after you await command.wait().

if (command.durationMs) {
  console.log(`Command ran for: ${command.durationMs}ms`);
}

Returns: number | undefined.

Use cmdId to identify the specific command execution so you can look it up later with sandbox.getCommand(). Store this value whenever you launch detached commands so you can replay output in dashboards or correlate logs across systems.

console.log(command.cmdId);

Returns: string.

The cwd accessor shows the working directory where the command is executing. Compare this value against expected paths when debugging file-related issues or verifying that relative paths resolve correctly.

console.log(command.cwd);

Returns: string.

startedAt returns the Unix timestamp (in milliseconds) when the command started executing. Subtract this from the current time to monitor execution duration or set timeout thresholds for long-running processes.

const duration = Date.now() - command.startedAt;
console.log(`Command has been running for ${duration}ms`);

Returns: number.

Call logs() to stream structured log entries in real time so you can watch command output as it happens. Each entry includes the stream type (stdout or stderr) and the data chunk, so you can route logs to different destinations or stop iteration when you detect a readiness signal.

for await (const log of command.logs()) {
  if (log.stream === 'stdout') {
    process.stdout.write(log.data);
  } else {
    process.stderr.write(log.data);
  }
}
ParameterTypeRequiredDetails
opts.signalAbortSignalNoCancel log streaming if needed.

Returns: AsyncGenerator<{ stream: "stdout" | "stderr"; data: string; }, void, void>.

Note: May throw StreamError if the sandbox stops while streaming logs.

Use wait() to block until a detached command finishes and get the resulting CommandFinished object with the populated exit code. This method is essential for detached commands where you need to know when execution completes. For non-detached commands, sandbox.runCommand() already waits automatically.

const detachedCmd = await sandbox.runCommand({
  cmd: 'sleep',
  args: ['5'],
  detached: true,
});
const result = await detachedCmd.wait();
if (result.exitCode !== 0) {
  console.error('Something went wrong...');
}
ParameterTypeRequiredDetails
params.signalAbortSignalNoCancel waiting if you need to abort early.

Returns: Promise<CommandFinished>.

Use output() to retrieve stdout, stderr, or both as a single string. Choose "both" when you want combined output for logging, or specify "stdout" or "stderr" when you need to process them separately after the command finishes.

const combined = await command.output('both');
const stdoutOnly = await command.output('stdout');
ParameterTypeRequiredDetails
stream"stdout" | "stderr" | "both"YesThe output stream to read.
opts.signalAbortSignalNoCancel output streaming.

Returns: Promise<string>.

Note: This may throw string conversion errors if the command output contains invalid Unicode.

stdout() collects the entire standard output stream as a string, which is handy when commands print JSON or other structured data that you need to parse after completion.

const output = await command.stdout();
const data = JSON.parse(output);
ParameterTypeRequiredDetails
opts.signalAbortSignalNoCancel the read while the command runs.

Returns: Promise<string>.

Note: This may throw string conversion errors if the command output contains invalid Unicode.

stderr() gathers all error output produced by the command. Combine this with exitCode to build user-friendly error messages or forward failure logs to your monitoring system.

const errors = await command.stderr();
if (errors) {
  console.error('Command errors:', errors);
}
ParameterTypeRequiredDetails
opts.signalAbortSignalNoCancel the read while collecting error output.

Returns: Promise<string>.

Note: This may throw string conversion errors if the command output contains invalid Unicode.

Call kill() to terminate a running command using the specified signal. This lets you stop long-running processes without destroying the entire sandbox. Send SIGTERM by default for graceful shutdown, or use SIGKILL for immediate termination.

await command.kill('SIGKILL');
ParameterTypeRequiredDetails
signalSignalNoThe signal to send to the process. Defaults to SIGTERM.
opts.abortSignalAbortSignalNoCancel the kill operation.

Returns: Promise<void>.

CommandFinished is the result you receive after a sandbox command exits. It extends the Command class, so you keep access to streaming helpers such as logs() or stdout(), but you also get the final exit metadata immediately. You usually receive this object from sandbox.runCommand() or by awaiting command.wait() on a detached process.

The exitCode property reports the numeric status returned by the command. A value of 0 indicates success; any other value means the process exited with an error, so branch on it before you parse output.

if (result.exitCode === 0) {
  console.log('Command succeeded');
}

Returns: number.

The durationMs property measures how long the command took to execute in milliseconds. Use this value to measure performance metrics or detect unusually long-running commands. The value is undefined for older commands that were executed before this property existed.

if (result.durationMs) {
  console.log(`Command ran for ${result.durationMs}ms`);
}

Returns: number | undefined.

Use cmdId to identify the specific command execution so you can reference it in logs or retrieve it later with sandbox.getCommand(). Store this ID whenever you need to trace command history or correlate output across retries.

console.log(result.cmdId);

Returns: string.

The cwd accessor shows the working directory where the command executed. Compare this value against expected paths when debugging file-related failures or relative path issues.

console.log(result.cwd);

Returns: string.

startedAt returns the Unix timestamp (in milliseconds) when the command started executing. Subtract this from the current time or from another timestamp to measure execution duration or schedule follow-up tasks.

const duration = Date.now() - result.startedAt;
console.log(`Command took ${duration}ms`);

Returns: number.

CommandFinished inherits all methods from Command including logs(), output(), stdout(), stderr(), and kill(). See the Command class section for details on these methods.

NetworkPolicy instances represent the firewall setup of the sandbox. To learn more, see network firewall.

The allow-all mode is the default applicable policy for sandboxes. It allows all egress traffic, to the Internet and secure-compute environments.

The deny-all mode can be set to restrict sandbox network access. It blocks all egress traffic, including DNS resolution.

Transformation and forwarding rules are available on Enterprise and Pro plans

The allow property allows the user to provide a list of website or API domains to allow access to. Traffic identification is based on SNI (server-name indicator), hence only TLS traffic is currently supported. Matching is based on:

  • if the domain does not contain any wildcard * segment, only exact matches are accepted.
  • if the domain includes a wildcard * as a middle segment (e.g. www.*.com), it only matches this one segment.
  • if the domain starts with a wildcard * (e.g. *.google.com), any subdomain is matched. It will not match the parent domain (e.g. google.com here).

Encryption is not intercepted if no transformation or forwarding rules are defined, allowing end-to-end data confidentiality.

The allow property can be set as an object providing the websites to allow traffic to, with additional transformation or forwarding rules. Only one of transform or forwardURL can be defined per rule. When such rules are defined, encryption is intercepted to allow request alteration.

Each rule can define a set of matchers on the path, method, query parameters, and headers. When defined, only requests matching the specified dimensions will be transformed or forwarded. Learn more about transformation rules and forwarding rules in the firewall documentation.

// Allow traffic only to the provided websites.
{
  "allow": ["ai-gateway.vercel.sh", "google.com"]
}
 
// Allow traffic to all websites and add transformations to specific ones.
{
  "allow": {
    "ai-gateway.vercel.sh": [{
      "transform": [{
        "headers": {
          "x-api-key": "secret-key"
        }
      }]
    }],
    "*.github.com": [{
      "match": {
        "method": {
          "exact": "POST"
        }
      },
      "forwardURL": "https://my-proxy.vercel.app/github"
    }],
    "*.openai.com": [{
      "match": {
        "path": {
          "startsWith": "/v1/chat/completions"
        }
      },
      "transform": [{
        "headers": {
          "x-api-key": "other-secret-key"
        }
      }]
    }],
    // Optionally allow traffic to all other domains.
    "*": []
  }
}

subnets.allow allows the user to provide a list of address ranges to allow traffic to. If used in combination with allow, traffic to those addresses will also bypass domain matching.

It enables users to enable traffic not using TLS, or towards systems where domains cannot be used. Beware of virtual hosting providers which can host many websites behind a given address.

subnets.deny allows the user to provide a list of address ranges to deny traffic to. Those ranges will always take precedence over subnets.allow and domain-based allow entries.

It allows the user to deny access to part of their network for instance while allowing access to the Internet in general.

A Snapshot represents a saved state of a sandbox that you can use to create new sandboxes. Snapshots capture the filesystem, installed packages, and environment configuration, letting you skip setup steps and start new sandboxes faster. To learn more, see Snapshots.

Create snapshots with sandbox.snapshot() or retrieve existing ones with Snapshot.get().

Use snapshotId to identify the snapshot when creating new sandboxes or retrieving it later. Store this ID to reuse the snapshot across multiple sandbox instances.

Returns: string.

index.ts
console.log(snapshot.snapshotId);

The sourceSessionId accessor returns the ID of the session that produced this snapshot. Use this to trace the origin of a snapshot or correlate it with session logs.

Returns: string.

index.ts
console.log(snapshot.sourceSessionId);

The status accessor reports the current state of the snapshot. Check this value to confirm the snapshot creation succeeded before using it.

Returns: "created" | "deleted" | "failed".

index.ts
console.log(snapshot.status);

The sizeBytes accessor returns the size of the snapshot in bytes. Use this to monitor storage usage.

Returns: number.

console.log(snapshot.sizeBytes);

All regions where the snapshot is available. Snapshots can only be used to create or resume sandboxes in a region where they are available.

Returns: string[].

console.log(snapshot.regions);

The createdAt accessor returns the date and time when the snapshot was created.

Returns: Date.

console.log(snapshot.createdAt);

The expiresAt accessor returns the date and time when the snapshot will automatically expire and be deleted, based on its last use. If the snapshot was created with expiration: 0, this value is null.

Returns: Date | null.

if (snapshot.expiresAt) {
  console.log(snapshot.expiresAt.toISOString());
}

Use Snapshot.list() to enumerate snapshots for a project. Filter by sandbox name, sort, and paginate using a cursor. The returned value is async-iterable and auto-paginates through every page.

Returns: Promise<Paginated<{ snapshots: Snapshot[]; pagination: Pagination; }>>.

ParameterTypeRequiredDetails
namestringNoFilter snapshots by sandbox name.
limitnumberNoMaximum number of snapshots per page.
cursorstringNoPagination cursor returned by the previous page.
sortOrder"asc" | "desc"NoSort direction. Defaults to "desc".
projectIdstringNoProject whose snapshots you want to list.
signalAbortSignalNoCancel the request.
const result = await Snapshot.list({ name: 'my-sandbox' });
for await (const snapshot of result) {
  console.log(snapshot.id, snapshot.status);
}

Use Snapshot.get() to retrieve an existing snapshot by its ID.

Returns: Promise<Snapshot>.

ParameterTypeRequiredDetails
snapshotIdstringYesIdentifier of the snapshot to retrieve.
signalAbortSignalNoCancel the request if necessary.
index.ts
import { Snapshot } from '@vercel/sandbox';
 
const snapshot = await Snapshot.get({ snapshotId: 'snap_abc123' });
console.log(snapshot.status);

Walk the parent-child ancestry tree of a snapshot. Set sortOrder: 'desc' to walk ancestors, or sortOrder: 'asc' to walk descendants. The returned value is async-iterable.

Returns: Promise<Paginated<{ snapshots: SnapshotTreeNodeData[]; pagination: Pagination; }>>.

ParameterTypeRequiredDetails
snapshotIdstringYesSnapshot ID to anchor the tree on.
sortOrder"asc" | "desc"No"desc" walks ancestors, "asc" walks descendants.
limitnumberNoMaximum number of nodes per page.
signalAbortSignalNoCancel the request.
const ancestors = await Snapshot.tree({
  snapshotId: 'snap_abc',
  sortOrder: 'desc',
});
for await (const node of ancestors) {
  console.log(node.snapshot.id, node.snapshot.parentId);
}

Call snapshot.delete() to remove a snapshot you no longer need. Deleting unused snapshots helps manage storage and keeps your snapshot list organized.

Returns: Promise<void>.

ParameterTypeRequiredDetails
opts.signalAbortSignalNoCancel the operation.
index.ts
await snapshot.delete();

Drives are available in Private Beta to Enterprise and Pro plans
Register your interest to get access

A Drive represents persistent storage that can be mounted into a sandbox. To learn more, see Drives.

Create drives with Drive.getOrCreate(), list them with Drive.list(), and delete them with drive.delete(). Mount them into sandboxes by using the mounts property in Sandbox.create().

Once you are added to the private beta, install the beta version of the @vercel/sandbox SDK:

Terminal
pnpm i @vercel/sandbox@beta
Terminal
yarn add @vercel/sandbox@beta
Terminal
npm i @vercel/sandbox@beta
Terminal
bun add @vercel/sandbox@beta

The name accessor returns the drive name. Drive names are unique within a Vercel project.

Returns: string.

index.ts
console.log(drive.name);

The projectId accessor returns the project ID that owns the drive.

Returns: string.

index.ts
console.log(drive.projectId);

The region accessor returns the region where the drive stores its data.

Returns: string.

index.ts
console.log(drive.region); // "iad1"

The maxSize accessor returns the configured drive size limit in bytes.

Returns: number.

index.ts
console.log(drive.maxSize);

The currentSessionId accessor returns the session ID the drive is attached to, if the drive is currently mounted.

Returns: string | undefined.

index.ts
console.log(drive.currentSessionId);

The currentSandboxName accessor returns the sandbox name the drive is attached to, if the drive is currently mounted.

Returns: string | undefined.

index.ts
console.log(drive.currentSandboxName);

The createdAt accessor returns the date and time when the drive was created.

Returns: Date.

index.ts
console.log(drive.createdAt);

The updatedAt accessor returns the date and time when the drive was last updated.

Returns: Date.

index.ts
console.log(drive.updatedAt);

Use Drive.list() to enumerate drives for a project. Filter by name prefix when you need to find drives for a specific workspace or user.

Returns: Promise<{ drives: Drive[]; pagination: Pagination; }> with async pagination helpers.

ParameterTypeRequiredDetails
projectIdstringNoProject whose drives you want to list.
limitnumberNoMaximum number of drives to return.
cursorstring | numberNoPagination cursor from a previous response.
sincenumber | stringNoLower pagination bound for returned drives.
untilnumber | stringNoUpper pagination bound for returned drives.
sortBy"createdAt" | "updatedAt" | "name"NoField to sort drives by.
sortOrder"asc" | "desc"NoSort direction.
namePrefixstringNoFilter drives by name prefix.
signalAbortSignalNoCancel the request if necessary.
index.ts
import { Drive } from '@vercel/sandbox';
 
const { drives, pagination } = await Drive.list({
  namePrefix: 'workspace',
  sortBy: 'name',
  sortOrder: 'asc',
  limit: 10,
});
 
for (const drive of drives) {
  console.log(drive.name, drive.currentSandboxName ?? 'detached');
}
 
console.log(pagination.next);

The returned object supports async iteration and helper methods for pagination:

index.ts
const result = await Drive.list({ limit: 10 });
 
for await (const drive of result) {
  console.log(drive.name);
}
 
const allDrives = await result.toArray();

Use Drive.getOrCreate() to retrieve an existing drive by name or create it if it doesn't exist. Create the drive before mounting it into a sandbox.

Returns: Promise<Drive>.

ParameterTypeRequiredDetails
namestringYesDrive name. Must be unique within the project.
regionstringNoRegion where the drive is created and stores its data. Defaults to iad1.
maxSizenumberNoDrive size limit in bytes. Defaults to 100 GiB when omitted, and can be configured up to 1 TiB.
signalAbortSignalNoCancel the request if necessary.

A drive's region can't change after creation. Calling Drive.getOrCreate() with a region or maxSize that doesn't match the existing drive fails with a conflict error.

index.ts
import { Drive } from '@vercel/sandbox';
 
const drive = await Drive.getOrCreate({
  name: 'workspace-cache',
  region: 'sfo1',
  maxSize: 200 * 1024 * 1024 * 1024, // 200 GiB
});

Call drive.delete() to permanently remove a drive and the data stored on it. The drive must not be attached to a sandbox when you delete it.

Returns: Promise<void>.

ParameterTypeRequiredDetails
opts.signalAbortSignalNoCancel the operation.
index.ts
await drive.delete();

Use defineSandboxProxy from @vercel/sandbox/proxy to implement a request-forwarding proxy referenced by a network policy forwardURL. The helper verifies the Vercel-issued OIDC token on each incoming request and extracts metadata about the source sandbox.

app/api/sandbox-proxy/route.ts
import { defineSandboxProxy } from '@vercel/sandbox/proxy';
 
const handler = defineSandboxProxy(async (request, meta) => {
  // meta: { host, teamId, projectId, sandboxId, sandboxName }
  console.log('Proxied from sandbox', meta.sandboxName);
  return fetch(request);
});
 
// Sandboxes forward requests using their original method, so expose the
// handler under every verb the network policy can route.
export {
  handler as GET,
  handler as POST,
  handler as PUT,
  handler as PATCH,
  handler as DELETE,
};

See the firewall documentation for the full request flow.

Vercel Sandbox supports two authentication methods:

  • Vercel OIDC tokens (recommended): Vercel generates the OIDC token that it associates with your Vercel project. For local development, run vercel link and vercel env pull to get a development token. In production on Vercel, authentication is automatic.
  • Access tokens: Use access tokens when VERCEL_OIDC_TOKEN is unavailable, such as in external CI/CD systems or non-Vercel environments.

To learn more on each method, see Authentication for complete setup instructions.

  • Image: Ubuntu 26.04 with common languages, coding agents, and utilities. See Vercel Managed Images to learn more.
  • Resources: Choose the number of virtual CPUs (vcpus) per sandbox. Pricing and plan limits appear in the Sandbox pricing table.
  • Timeouts: The default timeout is 5 minutes. You can extend it programmatically up to 45 minutes on the Hobby plan and up to 24 hours on Pro and Enterprise plans.
  • Sudo: sudo commands run as vercel-sandbox with the root home directory set to /root.

The filesystem is ephemeral. You must export artifacts to durable storage if you need to keep them after the sandbox stops.

Last updated August 21, 2026

Was this helpful?