Bun provides Bun.spawn() and Bun.spawnSync() for spawning child processes. Both APIs use posix_spawn(3) under the hood, making process spawning 60% faster than Node.js’s child_process module.
Bun.spawn()
Pass the command as an array of strings. Bun.spawn() returns a Subprocess object immediately.
const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.x.x\n"
await proc.exited; // wait for exit, resolves to exit code
Options
Configure the subprocess with a second argument:
const proc = Bun.spawn(["bun", "--version"], {
cwd: "./path/to/subdir", // working directory
env: { ...process.env, FOO: "bar" }, // environment variables
onExit(proc, exitCode, signalCode, error) {
// called when the process exits
},
});
proc.pid; // process ID of the subprocess
Stdin
By default, stdin is null (no input). Configure it with the stdin option:
| Value | Description |
|---|
null | Default. No input to the subprocess. |
"pipe" | Returns a FileSink for incremental writing. |
"inherit" | Inherit stdin from the parent process. |
Bun.file() | Read from a file. |
TypedArray | DataView | Use a binary buffer as input. |
Response | Use the response body as input. |
Request | Use the request body as input. |
ReadableStream | Use a readable stream as input. |
Blob | Use a blob as input. |
number | Read from a file descriptor. |
Passing a Response as stdin
const proc = Bun.spawn(["cat"], {
stdin: await fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js"),
});
const text = await proc.stdout.text();
console.log(text); // "const input = "hello world".repeat(400); ..."
Writing incrementally with “pipe”
const proc = Bun.spawn(["cat"], {
stdin: "pipe",
});
proc.stdin.write("hello");
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));
proc.stdin.flush(); // send buffered data
proc.stdin.end(); // close the stream
Passing a ReadableStream as stdin
const stream = new ReadableStream({
start(controller) {
controller.enqueue("Hello from ");
controller.enqueue("ReadableStream!");
controller.close();
},
});
const proc = Bun.spawn(["cat"], { stdin: stream, stdout: "pipe" });
const output = await proc.stdout.text();
console.log(output); // "Hello from ReadableStream!"
Stdout and stderr
By default, stdout is "pipe" (a ReadableStream) and stderr is "inherit" (passed through to the parent).
const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.x.x\n"
Configure output streams with the following values:
| Value | Description |
|---|
"pipe" | Default for stdout. Exposes a ReadableStream on the Subprocess. |
"inherit" | Default for stderr. Pass through to the parent process. |
"ignore" | Discard the output. |
Bun.file() | Write to a file. |
number | Write to a file descriptor. |
Exit handling
onExit callback
const proc = Bun.spawn(["bun", "--version"], {
onExit(proc, exitCode, signalCode, error) {
console.log(`Exited with code ${exitCode}`);
},
});
exited promise
proc.exited is a Promise that resolves to the exit code when the process finishes:
const proc = Bun.spawn(["bun", "--version"]);
const code = await proc.exited;
proc.killed; // boolean — was the process killed?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGTERM" | "SIGKILL" | ...
Killing a process
const proc = Bun.spawn(["sleep", "10"]);
proc.kill(); // sends SIGTERM
proc.kill(9); // sends signal number 9
proc.kill("SIGKILL"); // sends SIGKILL
proc.killed; // true
Detaching a process
By default, the parent bun process waits for all child processes to exit. Use proc.unref() to detach the child from the parent’s lifetime:
const proc = Bun.spawn(["some-background-service"]);
proc.unref(); // parent can exit without waiting for this process
Timeout and AbortSignal
Automatic timeout
// Automatically kill the process after 5 seconds
const proc = Bun.spawn({
cmd: ["sleep", "10"],
timeout: 5000, // milliseconds
});
await proc.exited; // resolves after 5 seconds
By default, timed-out processes are killed with SIGTERM. Use killSignal to specify a different signal:
const proc = Bun.spawn({
cmd: ["sleep", "10"],
timeout: 5000,
killSignal: "SIGKILL",
});
AbortSignal
const controller = new AbortController();
const proc = Bun.spawn({
cmd: ["sleep", "100"],
signal: controller.signal,
});
// Later, abort the process
controller.abort();
Resource usage
After a process exits, inspect its resource consumption:
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited;
const usage = proc.resourceUsage();
console.log(`Max memory: ${usage.maxRSS} bytes`);
console.log(`CPU user time: ${usage.cpuTime.user} µs`);
console.log(`CPU system time: ${usage.cpuTime.system} µs`);
Inter-process communication (IPC)
Bun supports a direct IPC channel between two bun processes. Specify an ipc handler when spawning:
const child = Bun.spawn(["bun", "child.ts"], {
ipc(message, childProc) {
console.log("Received from child:", message);
childProc.send("Hello back from parent");
},
});
child.send("Hello from parent");
In the child process, use process.send() and process.on("message"):
process.send("Hello from child");
process.on("message", message => {
console.log("Received from parent:", message);
});
The serialization option controls how messages are encoded between processes:
| Value | Description |
|---|
"advanced" | Default. Uses JSC’s serialize API — supports everything structuredClone supports. |
"json" | Uses JSON.stringify / JSON.parse. Required for IPC between Bun and Node.js. |
IPC between Bun and Node.js
Set serialization: "json" when communicating with a Node.js process:
if (typeof Bun !== "undefined") {
const node = Bun.spawn({
cmd: ["node", __filename],
ipc({ message }) {
console.log(message);
node.send({ message: "Hey from Bun!" });
node.kill();
},
stdio: ["inherit", "inherit", "inherit"],
serialization: "json",
});
node.send({ message: "Hey Node!" });
} else {
process.on("message", ({ message }) => {
console.log(message);
process.send({ message: "Hey Bun!" });
});
}
To disconnect the IPC channel from the parent:
Terminal (PTY) support
For interactive terminal applications, spawn a subprocess with a pseudo-terminal attached using the terminal option:
const proc = Bun.spawn(["bash"], {
terminal: {
cols: 80,
rows: 24,
data(terminal, data) {
process.stdout.write(data);
},
},
});
proc.terminal.write("echo hello\n");
await proc.exited;
proc.terminal.close();
When using terminal, the subprocess sees process.stdout.isTTY as true, and proc.stdin / proc.stdout / proc.stderr are null — use proc.terminal instead.
PTY support is only available on POSIX systems (Linux and macOS). It is not available on Windows.
Bun.spawnSync()
Bun.spawnSync() is a synchronous, blocking equivalent of Bun.spawn(). It returns a SyncSubprocess with stdout and stderr as Buffers.
const proc = Bun.spawnSync(["echo", "hello"]);
console.log(proc.stdout.toString()); // => "hello\n"
console.log(proc.success); // => true (exit code 0)
console.log(proc.exitCode); // => 0
Use Bun.spawn() for HTTP servers and long-running applications. Use Bun.spawnSync() for CLI tools and scripts where blocking is acceptable.
maxBuffer
Limit the number of bytes of output before the process is killed:
const result = Bun.spawnSync({
cmd: ["yes"],
maxBuffer: 100, // kill after 100 bytes of output
});
Node.js compatibility
Bun is fully compatible with Node.js’s child_process module:
import { spawn, spawnSync, execSync, fork } from "child_process";
const child = spawn("ls", ["-la"]);
child.stdout.on("data", data => console.log(data.toString()));
const result = spawnSync("echo", ["hello"]);
console.log(result.stdout.toString()); // => "hello\n"
For IPC using child_process.fork(), use process.send() and process.on("message") in the child — the same API as Bun’s native IPC.