child_process
child_process runs external programs from Node - prefer spawn with explicit argument arrays, understand stdio modes, and avoid shell injection when wrapping CLI tools.
Search across all documentation pages
child_process runs external programs from Node - prefer spawn with explicit argument arrays, understand stdio modes, and avoid shell injection when wrapping CLI tools.
import { spawn } from 'node:child_process';
const child = spawn('git', ['rev-parse', 'HEAD'], { stdio: ['ignore', 'pipe', 'pipe'] });import { promisify } from 'node:util';
import { execFile } from 'node:child_process';
const execFileAsync = promisify(execFile);
const { stdout } = await execFileAsync('node', ['--version']);When to reach for this:
ffmpeg CLIimport { spawn } from 'node:child_process';
import { once } from 'node:events';
async function runGitHash(): Promise<string> {
const child = spawn('git', ['rev-parse', 'HEAD'], {
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdout = '';
child.stdout.on('data', (chunk: Buffer) => {
stdout += chunk.toString('utf8');
});
const [code] = await once(child, 'exit');
if (code !== 0) throw new Error(`git exited ${code}`);
return stdout.trim();
}
// Dangerous - do not do this with user input:
// spawn(`git rev-parse ${userBranch}`, { shell: true });import { pipeline } from 'node:stream/promises';
import { spawn } from 'node:child_process';
import { createWriteStream } from 'node:fs';
async function compressWithGzip(input: string, output: string): Promise<void> {
const gzip = spawn('gzip', ['-c', input], { stdio: ['ignore', 'pipe', 'inherit'] });
await pipeline(gzip.stdout, createWriteStream(output));
const code = await new Promise<number>((res) => gzip.on('exit', res));
if (code !== 0) throw new Error(`gzip failed ${code}`);
}What this demonstrates:
execve without shell interpretationexec buffer limits for large outputpipeline for backpressureshell: true only for trusted fixed commands - never with user stringsspawn - streams stdio, returns immediately, exit event with code.exec - buffers stdout/stderr, invokes shell by default - injection risk.execFile - no shell, buffered output with maxBuffer default 1MB.fork - special Node child with IPC channel - legacy pattern before worker_threads.| API | Shell | Output | Use |
|---|---|---|---|
| spawn | No (default) | Stream | Long output, pipes |
| exec | Yes (default) | Buffered | Small trusted commands |
| execFile | No | Buffered | Small args array |
| fork | No | IPC | Node-only workers (legacy) |
import type { ChildProcess } from 'node:child_process';
export function killProcessTree(child: ChildProcess): void {
if (child.pid) process.kill(-child.pid, 'SIGTERM');
}Platform-specific - Linux process groups need detached: true on spawn.
shell: true + user input - command injection. Fix: spawn(cmd, [arg1, arg2]) without shell.exec maxBuffer exceeded - throws on large stdout. Fix: spawn with streams.exit handler or await exit in long-running supervisors.stdio: 'ignore' for background jobs.| Alternative | Use When | Don't Use When |
|---|---|---|
| worker_threads | CPU JS in same runtime | Need separate binary or sandbox OS process |
| Pure Node library | ffmpeg wasm, sharp vs CLI | Mature CLI already scripted |
| Container exec | K8s job per task | Simple local script |
node:child_process test mocks | Unit tests | Production orchestration |
spawn for streaming and safety; exec for quick small trusted shell one-liners in dev scripts only.
Child shares parent console - good for CLI tools showing live output to terminal.
spawn(cmd, args, { env: { ...process.env, FOO: 'bar' } }).
Yes - stdio: ['pipe', 'pipe', 'pipe'] and write to child.stdin.
code null and signal set on kill - handle SIGTERM in child scripts.
setTimeout + child.kill('SIGKILL') with cleanup - or use timers/promises + AbortSignal patterns in wrappers.
Not removed - prefer worker_threads for CPU; fork for Node IPC legacy code.
Search shell: true and string-concatenated commands in PRs.
Pipe stderr to logger - stdio: ['ignore', 'pipe', 'pipe'] and tag child stderr lines.
cluster forks Node workers; spawn runs arbitrary executables.
execFile('git', ['diff', '--name-only']) - safe and deterministic.
.cmd files may need shell: true - prefer node/npm with explicit paths documented for team.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 19, 2026