child_process.spawn()
Launch a command in a new process and stream its stdout and stderr back as Node streams.
child_process.spawn(command[, args][, options])- Added in Node.js v0.1.90
Parameters
| Parameter | Type | Description |
|---|---|---|
$command | <string> | The command to run. Without a shell this is an executable name or path, not a command line. |
$argsoptional | <string[]>= [] | List of string arguments. Each element becomes one argv entry and is not word-split or glob-expanded. |
$optionsoptional | <Object> | Spawn options. |
$options.cwdoptional | <string> | <URL>= process.cwd() | Current working directory of the child process. |
$options.envoptional | <Object>= process.env | Environment key-value pairs. The object replaces the child's whole environment; it is not merged. |
$options.argv0optional | <string>= command | Explicitly set the value of argv[0] sent to the child process. |
$options.stdiooptional | <Array> | <string>= 'pipe' | Child's stdio configuration: 'pipe', 'inherit', 'ignore', 'overlapped', or an array of three entries. |
$options.detachedoptional | <boolean>= false | Prepare the child process to run independently of its parent process. |
$options.shelloptional | <boolean> | <string>= false | If true, runs command inside a shell ('/bin/sh' on Unix, process.env.ComSpec on Windows). |
$options.serializationoptional | <string>= 'json' | Kind of serialization used for IPC messages: 'json' or 'advanced'. |
$options.signaloptional | <AbortSignal> | Allows aborting the child process using an AbortSignal. |
$options.timeoutoptional | <number>= undefined | Maximum milliseconds the process is allowed to run before it is sent killSignal. |
$options.killSignaloptional | <string> | <integer>= 'SIGTERM' | Signal used when the process is killed by timeout or abort signal. |
$options.uidoptional | <number> | Sets the user identity of the process (see setuid(2)). |
$options.gidoptional | <number> | Sets the group identity of the process (see setgid(2)). |
$options.windowsHideoptional | <boolean>= false | Hide the subprocess console window that would normally be created on Windows. |
$options.windowsVerbatimArgumentsoptional | <boolean>= false | No quoting or escaping of arguments is done on Windows. Ignored on Unix. |
Return value
<ChildProcess>A ChildProcess instance whose stdout and stderr are readable streams and whose stdin is writable (unless the stdio option says otherwise).
Errors
Throws ERR_INVALID_ARG_TYPE or ERR_INVALID_ARG_VALUE synchronously for bad arguments, but a command that cannot be found or executed is reported asynchronously as an 'error' event, not a thrown exception.
Changelog
- PHP v23.11.0, v22.15.0Passing args together with shell: true is deprecated.
- PHP v16.4.0, v14.18.0The cwd option can be a WHATWG URL object using the file: protocol.
- PHP v15.13.0, v14.18.0The timeout option was added.
- PHP v15.5.0, v14.17.0AbortSignal support was added via the signal option.
- PHP v13.2.0, v12.16.0The serialization option ('advanced' structured-clone IPC) is supported.
- PHP v8.8.0The windowsHide option is supported.
- PHP v5.7.0The shell option is supported.
spawn() is the primitive every other child_process function is built on. It starts the process, returns immediately, and hands you the child's streams. Nothing is buffered for you.
When to reach for it
Use spawn() when output is large, unbounded, or wanted as it arrives: build tools, ffmpeg, git log, a tail of a log file. Because there is no maxBuffer, a command producing hundreds of megabytes is fine here and fatal with exec(). Use it also when you want stdio: 'inherit' so the child writes straight to your terminal.
What trips people up
- The command is not a command line.
spawn('ls -l')looks for an executable literally namedls -land fails with ENOENT. Arguments belong inargs. - A missing binary arrives as an
'error'event. With no listener attached, that event throws and takes the process down. - On Windows,
.cmdand.batwrappers (includingnpm) are not executables; they needshell: true. - Piped streams apply backpressure. If you never read
stdout, a chatty child eventually blocks on a full pipe and appears to hang.
Prefer execFile() when the output is small and you just want it as a string.
Example
const { spawn } = require('node:child_process');
// Arguments are an array; the command itself is never a command line.
const child = spawn('node', [
'-e',
'for (let i = 1; i <= 3; i++) console.log("line " + i)',
]);
let out = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => { out += chunk; });
child.stderr.setEncoding('utf8');
child.stderr.on('data', (chunk) => console.log('stderr:', chunk.trim()));
// Always attach this: a missing binary arrives here, not as a throw.
child.on('error', (err) => console.log('failed to start:', err.code));
child.on('close', (code, signal) => {
console.log('exit code:', code);
console.log('signal:', signal);
console.log('stdout:', JSON.stringify(out));
});
// exit code: 0
// signal: null
// stdout: "line 1\nline 2\nline 3\n"When to use it
- Run ffmpeg or ImageMagick and forward its progress output to a log as it is produced.
- Shell out to git and parse a long history stream line by line.
- Run a build tool with stdio: 'inherit' so its coloured output reaches the user's terminal.
Frequently asked questions
What is the difference between spawn and exec in Node.js?
spawn() streams the child's output and buffers nothing, while exec() runs the command through a shell and collects all output into a single string passed to a callback. Use spawn() for long-running or high-volume commands, and exec() only for short output that fits comfortably under the 1 MiB maxBuffer default.
Why does child_process.spawn throw ENOENT?
ENOENT means the executable named in command was not found on PATH, and it almost always happens because the whole command line was passed as command instead of splitting arguments into the args array. Check that command is a bare executable name, that PATH survived any custom env option, and on Windows that .cmd wrappers are run with shell: true.
How do I get the output of spawn as a string?
Set an encoding on the stream and concatenate the chunks, for example child.stdout.setEncoding('utf8') then collecting 'data' events into a variable and reading it in the 'close' handler. If you only ever want the finished string, execFile() does the buffering for you.
More examples
Piping one process into another
Piping producer.stdout into sorter.stdin reproduces a shell pipeline while keeping both sides as ordinary streams. The output shows the three lines sorted, and sort's exit code of 0 confirms it saw a clean end-of-input when the producer's stdout closed.
const { spawn } = require('node:child_process');
const producer = spawn('node', ['-e', 'console.log("b\\na\\nc")']);
const sorter = spawn('sort');
// Connect the two the way a shell pipeline would, without a shell.
producer.stdout.pipe(sorter.stdin);
let sorted = '';
sorter.stdout.setEncoding('utf8');
sorter.stdout.on('data', (chunk) => { sorted += chunk; });
sorter.on('close', (code) => {
console.log('sort exited with', code);
console.log(JSON.stringify(sorted));
});
// sort exited with 0
// "a\nb\nc\n"Killing a runaway child with a timeout or AbortSignal
Both children are killed rather than exiting on their own, so code is null and signal names the signal that ended them. Aborting also emits an 'error' event with an AbortError, which is why an 'error' listener is mandatory here: without it the abort would crash the parent.
const { spawn } = require('node:child_process');
// Option 1: let spawn enforce the limit.
const a = spawn('node', ['-e', 'setInterval(() => {}, 1000)'], {
timeout: 500,
killSignal: 'SIGKILL',
});
a.on('close', (code, signal) => {
console.log('timeout child ->', code, signal); // timeout child -> null SIGKILL
});
// Option 2: cancel it yourself from elsewhere in the program.
const controller = new AbortController();
const b = spawn('node', ['-e', 'setInterval(() => {}, 1000)'], {
signal: controller.signal,
});
b.on('error', (err) => {
console.log('abort error name:', err.name); // abort error name: AbortError
});
b.on('close', (code, signal) => {
console.log('aborted child ->', code, signal); // aborted child -> null SIGTERM
});
controller.abort();Detaching a child so it outlives the parent
detached: true puts the child in its own process group and unref() removes it from the parent's reference count, so the parent can exit while the job keeps running. The stdio entries must not be pipes, since those would die with the parent; here they are file descriptors pointing at a log file. The pid is printed as a type rather than a value because it differs on every run.
const { spawn } = require('node:child_process');
const fs = require('node:fs');
const log = fs.openSync('./worker.log', 'a');
const child = spawn('node', ['./long-job.js'], {
detached: true,
stdio: ['ignore', log, log], // no pipes back to the parent
});
console.log('started detached child, pid is a number:', typeof child.pid);
// Stop counting the child in the parent's event loop, then exit.
child.unref();
// started detached child, pid is a number: number
Discussion