Skip to content
sandbox-workers
Esc
↑↓navigate↵open⌘Jpreview
On this page

Environment variables

Guest envVars per call and per context, setEnvVars layering, per-language access, the SANDBOX_IDLE_TTL_MS / INTERPRETER_IDLE_TTL_MS settings, and disabling the File API with SANDBOX_FILE_API.

Environment variables work at two layers: guest-visible envVars, passed into executing code, and Worker vars, which configure a Worker itself (Wrangler’s own environment variables) — and on this side, there are two separate idle-TTL settings, one per Worker.

Guest environment variables

envVars works the same way in both modes — pass it as a call option. In stateless mode:

import { runCode } from "@sandbox-workers/core";

await runCode(env.PYTHON, code, { envVars: { X: "12" } });

In stateful mode:

await sandbox.interpreter.runCode(code, { envVars: { X: "12" } });

Keys must match /^[A-Za-z_][A-Za-z0-9_]*$/; values must be strings. A null or undefined value is skipped rather than passed through. Only the key/value pairs supplied this way — merged across the layers below in stateful mode — are visible to guest code; nothing from either Worker’s own host environment leaks through.

Each language reads them differently:

Language Access
JavaScript process.env.NAME
Python os.environ["NAME"]
Perl $ENV{NAME}
Ruby ENV["NAME"]

Sandbox-level: setEnvVars (stateful mode only)

sandbox.setEnvVars() sets variables for every execution in the sandbox, across every context and every binding, until changed again:

await sandbox.setEnvVars({ NAME: "value", OLD: undefined }); // undefined unsets a key

Passing undefined for a key removes it rather than setting the literal string "undefined".

Context-level: envVars on createCodeContext (stateful mode only)

A code context can carry its own envVars, set once when the context is created:

const ctx = await sandbox.interpreter.createCodeContext({ binding: "PYTHON", envVars: { MODE: "test" } });

Layering order

When the same key is set at more than one layer, a later source overrides an earlier one, in this order:

  1. Sandbox-level (setEnvVars)
  2. Context-level (createCodeContext({ envVars }))
  3. Call-level (runCode(code, { envVars }))

An explicit null/undefined at any layer unsets that key for the merged result rather than passing the literal string through to the guest. This full merge happens in the Sandbox Durable Object, which sends the flat, already-merged result to the runtime Worker.

Two idle-TTL settings, on two Workers (stateful mode only)

Both settings below apply only to stateful mode: a stateless call has no sandbox and no code context to expire. Because a sandbox and a code context’s interpreter live in different Durable Objects — possibly in Workers you deployed independently — there are two separate settings:

SANDBOX_IDLE_TTL_MS — your own Worker

Configures how long a sandbox — every code context’s registry entry plus /workspace — can sit idle before your Sandbox Durable Object deletes it:

// your wrangler.jsonc
{
  "vars": { "SANDBOX_IDLE_TTL_MS": "86400000" }, // 24 hours (the default); "0" disables expiry
}

INTERPRETER_IDLE_TTL_MS — each runtime Worker

Configures how long a runtime Worker’s Interpreter Durable Object keeps a given sandbox’s contexts’ memory snapshots before wiping them:

// this runtime Worker's wrangler.jsonc
{
  "vars": { "INTERPRETER_IDLE_TTL_MS": "3600000" }, // 1 hour; "0" disables expiry
}

Both default to 24 hours (Wrangler vars are always strings; unset or invalid values fall back to the default), and both treat "0" as “never expire.”

Set INTERPRETER_IDLE_TTL_MS to at least SANDBOX_IDLE_TTL_MS. If a runtime Worker’s interpreter expires first, its memory snapshots are gone while the sandbox’s registry still lists the context as live — the next runCode against it fails with ContextNotFoundError even though sandbox.getInfo() still shows the context. See Sandboxes for the full idle-expiry behavior on both sides.

Disabling the File API (stateful mode only)

SANDBOX_FILE_API — your own Worker

Turns the File API off for every sandbox your Sandbox Durable Object hosts:

// your wrangler.jsonc
{
  "vars": { "SANDBOX_FILE_API": "disabled" }, // optional; unset or any other value keeps it enabled
}

When disabled:

  • Every file method (writeFile, readFile, mkdir, deleteFile, renameFile, moveFile, listFiles, exists) throws NotSupportedError (code: "NOT_SUPPORTED", HTTP 403) instead of running. On the wire, POST /files answers 403. See Errors.
  • /workspace is never persisted: the Sandbox Durable Object neither reads nor writes its files table, and the workspace diff a runtime Worker returns after runCode is discarded.
  • Guest code loses /workspace too. Every runCode sends an empty workspace manifest with disabled: true, and the interpreter refuses every read, write, mkdir, delete, rename, and directory listing under /workspace with EACCES (a JavaScript fs.* Error with code: "EACCES", a Python PermissionError, a Perl open failure with $! set to Permission denied, and a failed import/require of a module from /workspace). process.cwd()/os.getcwd() still return /workspace — the directory still exists as the cwd, just empty and inaccessible. Memory snapshots, globals, and code contexts are unaffected.
  • sandbox.getInfo() reports fileApi: false, and workspace then always reports { files: 0, bytes: 0 }. See Lifecycle.
  • This guest-side lockout requires runtime Workers built from a version that understands the disabled manifest flag — an older runtime Worker ignores it. Nothing is stored either way (the sandbox still discards its writes), but guest code on an older runtime Worker wouldn’t see EACCES.
  • Wrangler configuration — where vars and other Worker settings live.
  • Execute code — passing envVars to runCode.
  • Code interpreter — createCodeContext, setEnvVars, and runCode signatures.
  • Sandboxes — idle expiry behavior in full, including what happens when the timers mismatch.
  • Files — the File API this setting disables, and its error handling.