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 sandbox — vm 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
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) // 225 2
vm.Script — compile once, run many times
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) // 30vm.createContext — controlling the global scope
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
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.Module — ES module support
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)) // 42Practical use cases
Use case | Approach |
|---|---|
Template engine (interpolate expressions in text) |
|
Rule evaluation engine (e.g. pricing rules, feature flags) |
|
Test isolation (reset module state between tests) |
|
REPL / playground |
|
Untrusted user code | Do NOT use vm — use |