NodeJSThe vm Module

The vm Module

The node:vm module compiles and runs JavaScript code in a V8 context — an isolated global scope that you create and control. Code executed in a vm context sees only the globals you explicitly provide: no require, no process, no fs unless you inject them. This makes the vm module useful for running user-provided scripts, implementing template engines, testing code in isolation, and creating sandboxed rule evaluation. It is not a security sandboxvm contexts share the same V8 heap as the host, and a determined attacker can escape the context. For untrusted code, use a separate process or a Wasm sandbox.

vm.runInNewContext — execute a string of code

TS
import vm from 'node:vm'

// Run code in a new context with only the globals you provide:
const result = vm.runInNewContext(
  'x * 2 + y',
  { x: 10, y: 5 }    // sandbox — the context's global scope
)
console.log(result)   // 25

// The sandbox object is modified in place by the code:
const sandbox = { count: 0 }
vm.runInNewContext('count += 1; count += 1', sandbox)
console.log(sandbox.count)   // 2
25
2
vm.Script — compile once, run many times

TS
import vm from 'node:vm'

// Compile the script once (expensive V8 parse + compile step),
// then run it against different contexts (cheap):
const script = new vm.Script('result = a + b')

const ctx1 = vm.createContext({ a: 1, b: 2, result: 0 })
const ctx2 = vm.createContext({ a: 10, b: 20, result: 0 })

script.runInContext(ctx1)
script.runInContext(ctx2)

console.log(ctx1.result)   // 3
console.log(ctx2.result)   // 30
Compile scripts with new vm.Script once and run them in multiple contexts — avoid re-parsing the same source string on every invocation
V8 parsing and compilation is expensive relative to execution for small scripts. If you're running the same script template against many contexts (e.g. a rule evaluation engine that applies the same expression to thousands of records), compiling once with `new vm.Script()` and calling `runInContext()` repeatedly avoids redundant compilation. The compiled `Script` object caches the bytecode. For one-off script execution, `vm.runInNewContext` is fine — it compiles and runs in one call.
vm.createContext — controlling the global scope

TS
import vm from 'node:vm'

// createContext "contextifies" a plain object — it becomes the global scope:
const context = vm.createContext({
  // Provide only what the script should have access to:
  Math,
  console: { log: (...args: unknown[]) => console.log('[sandbox]', ...args) },
  fetch: globalThis.fetch,   // allow network calls if desired
  // No require, no process, no fs — unless explicitly added
})

const script = new vm.Script(`
  const result = Math.sqrt(16) + Math.PI
  console.log('Result:', result)
  result
`)

const value = script.runInContext(context)
console.log('Returned:', value)
[sandbox] Result: 7.141592653589793
Returned: 7.141592653589793
Timeout — prevent infinite loops

TS
import vm from 'node:vm'

try {
  vm.runInNewContext(
    'while(true) {}',    // infinite loop
    {},
    { timeout: 100 }     // milliseconds — throws after 100ms
  )
} catch (err) {
  console.log(err instanceof Error && err.message)
  // "Script execution timed out after 100ms"
}
vm is NOT a security sandbox — a script can escape a vm context and access the host's require/process via prototype chain traversal or well-known escape techniques
The vm module isolates the **global scope** — the script can't access `require` or `fs` directly. But it still runs in the same V8 heap and process as the host. A script can escape the sandbox by exploiting prototype chain access: `({}).constructor.constructor('return process')()` gives access to the host's `process` object from within a vm context. This is a well-known, documented limitation. For running truly untrusted code (user-submitted scripts, plugin systems from unknown authors), use a **separate child process** (`child_process.fork`) or a dedicated sandboxing solution. The vm module is appropriate for **trusted but isolated** scenarios: running template expressions, implementing eval-like features in your own tooling, or isolating modules in a test harness — not for user-submitted code from the internet.
vm.Module — ES module support

TS
import vm from 'node:vm'

// vm.SourceTextModule is the ESM equivalent of vm.Script — experimental:
const mod = new vm.SourceTextModule(`
  export const greeting = 'hello from vm module'
  export function double(x) { return x * 2 }
`, { context: vm.createContext({}) })

await mod.link(() => {})      // resolve imports (none in this example)
await mod.evaluate()           // execute the module

const ns = mod.namespace as { greeting: string; double: (x: number) => number }
console.log(ns.greeting)       // hello from vm module
console.log(ns.double(21))     // 42
vm.SourceTextModule is experimental (--experimental-vm-modules flag required) — use it for testing ES module code in isolation, not in production
`vm.SourceTextModule` lets you create an ES module (with `import`/`export`) inside a vm context. It's primarily useful for test harnesses that need to evaluate ES modules in isolation (e.g., a test runner that intercepts module imports). Enable it with `node --experimental-vm-modules`. The `link()` method is where you intercept import requests and provide mock modules. The API is more complex than `vm.Script` because ESM has a separate linking phase. Unless you're building a test framework or REPL, you're unlikely to need this API.
Practical use cases

Use case

Approach

Template engine (interpolate expressions in text)

vm.Script per compiled template; run with user-supplied data sandbox

Rule evaluation engine (e.g. pricing rules, feature flags)

new vm.Script(ruleExpression) compiled once; runInContext per evaluation

Test isolation (reset module state between tests)

vm.createContext with fresh require-like shim per test suite

REPL / playground

vm.runInContext with accumulated state sandbox; timeout to prevent hangs

Untrusted user code

Do NOT use vm — use child_process.fork with IPC or a Wasm sandbox

Next
A tour of what's new in recent Node.js versions: [Modern Node.js Features](/nodejs/modern-features).