child_process.execSync()
Run a shell command, block until it finishes, and return its stdout.
child_process.execSync(command[, options])- Added in Node.js v0.11.12
Parameters
| Parameter | Type | Description |
|---|---|---|
$command | <string> | The command to run, interpreted by a shell. |
$optionsoptional | <Object> | Execution options. |
$options.cwdoptional | <string> | <URL>= process.cwd() | Current working directory of the child process. |
$options.inputoptional | <string> | <Buffer> | <TypedArray> | <DataView> | Value passed as stdin to the spawned process. Supplying it overrides stdio[0]. |
$options.stdiooptional | <string> | <Array>= 'pipe' | Child's stdio configuration. The default pipes stdout back to you; 'inherit' sends the child's output straight to the terminal. |
$options.envoptional | <Object>= process.env | Environment key-value pairs, replacing the child's environment entirely. |
$options.shelloptional | <string>= '/bin/sh' on Unix, process.env.ComSpec on Windows | Shell to execute the command with. |
$options.timeoutoptional | <number>= undefined | Maximum milliseconds the process is allowed to run before killSignal is sent. |
$options.killSignaloptional | <string> | <integer>= 'SIGTERM' | Signal used when the spawned process is killed. |
$options.maxBufferoptional | <number>= 1024 * 1024 | Largest amount of data in bytes allowed on stdout or stderr. |
$options.encodingoptional | <string>= 'buffer' | Encoding used for all stdio inputs and outputs. Note this defaults to 'buffer', not 'utf8'. |
$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. |
Return value
<Buffer> | <string>The stdout from the command. A Buffer by default, or a string when the encoding option is set.
Errors
Throws if the process exits non-zero, times out, or cannot be spawned. The thrown Error carries status, signal, stdout, stderr and output, and error.stderr is the only place the child's message can be read once stdio is piped.
Changelog
- PHP v16.4.0, v14.18.0The cwd option can be a WHATWG URL object using the file: protocol.
- PHP v10.10.0The input option can be any TypedArray or a DataView.
- PHP v8.8.0The windowsHide option is supported.
- PHP v8.0.0The input option can be a Uint8Array.
execSync() runs a command through a shell and blocks the entire Node process — event loop, timers, sockets and all — until it exits, then returns whatever the command wrote to stdout.
When to reach for it
Only in code where blocking is free: build scripts, CLI tools, npm lifecycle hooks, migrations, one-shot setup steps. Reading a git SHA at the top of a build script is the canonical use. Never call it inside a request handler or any long-lived server — one slow command freezes every concurrent connection.
What trips people up
- The
encodingdefault is'buffer', not'utf8'. Forgetting{ encoding: 'utf8' }gives you a Buffer, and the almost-inevitable.trim()then fails. - Failure throws. A non-zero exit is an exception, so anything that can fail needs a
try/catch; readerr.statusanderr.stderrthere. - With the default
stdio: 'pipe'the child's stderr is captured, so error output vanishes unless you printerr.stderryourself.stdio: 'inherit'shows it live but then makes the return value null. - The command string is shell syntax, so interpolating a variable is a command injection hole. Use spawnSync() with an args array instead.
Example
const { execSync } = require('node:child_process');
// Without encoding this would return a Buffer.
const out = execSync('echo hello', { encoding: 'utf8' });
console.log('stdout:', JSON.stringify(out));
console.log('type:', typeof out);
// A non-zero exit throws rather than returning a status.
try {
execSync('echo boom 1>&2; exit 3', { encoding: 'utf8', stdio: 'pipe' });
} catch (err) {
console.log('status:', err.status);
console.log('stderr:', JSON.stringify(err.stderr));
}
// stdout: "hello\n"
// type: string
// status: 3
// stderr: "boom\n"When to use it
- Read the current git commit or branch at the start of a build script.
- Run a database migration command in a deployment script and abort on failure.
- Shell out to a packaging tool from a CLI where blocking is acceptable.
Frequently asked questions
How do I run a command synchronously in Node.js?
Call child_process.execSync(command, { encoding: 'utf8' }), which blocks until the command exits and returns its stdout as a string. Wrap it in try/catch, because a non-zero exit code throws instead of returning a status.
Why does execSync return a Buffer instead of a string?
Because the encoding option defaults to 'buffer', unlike exec() which defaults to 'utf8'. Pass { encoding: 'utf8' } to get a string back.
How do I see the error message when execSync fails?
Catch the thrown Error and read err.stderr, since the default stdio: 'pipe' captures the child's error output instead of printing it. The same Error also carries err.status for the exit code and err.stdout for anything printed before the failure.
More examples
Reading build metadata in a script
This is the pattern execSync() exists for: a build script that needs a value before it can continue, where blocking costs nothing. The output prints the shape rather than the values because a commit hash differs in every checkout. Setting stdio[0] to 'ignore' stops git from ever trying to read from the terminal, which is what causes a build to hang when a command unexpectedly prompts.
const { execSync } = require('node:child_process');
function git(args) {
return execSync(`git ${args}`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
}
let meta;
try {
meta = { commit: git('rev-parse --short HEAD'), branch: git('rev-parse --abbrev-ref HEAD') };
} catch (err) {
// Not a git checkout, or git is not installed.
console.log('git unavailable, status:', err.status);
meta = { commit: 'unknown', branch: 'unknown' };
}
console.log('keys:', Object.keys(meta).sort());
// keys: [ 'branch', 'commit' ]Streaming output to the terminal with stdio: inherit
With stdio: 'inherit' the child shares the parent's file descriptors, so its output appears live and in colour. The trade-off is visible in the last line: nothing was piped back, so the return value is null. Choose 'inherit' when a human is watching, and the default 'pipe' when your code needs the text.
const { execSync } = require('node:child_process');
// 'inherit' lets the child write directly to this terminal, keeping colours
// and showing progress as it happens instead of after it finishes.
const result = execSync('echo step 1 && echo step 2', { stdio: 'inherit' });
console.log('return value:', result);
// step 1
// step 2
// return value: nullFeeding stdin with the input option
input writes the given string or Buffer to the child's stdin and then closes it, which is how you drive a filter command without a temporary file. Supplying input overrides whatever stdio[0] would have been, so the child never touches the parent's terminal.
const { execSync } = require('node:child_process');
const payload = 'gamma\nalpha\nbeta\n';
const sorted = execSync('sort', {
input: payload,
encoding: 'utf8',
});
console.log(JSON.stringify(sorted));
// "alpha\nbeta\ngamma\n"
Discussion