Skip to main content
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:
ValueDescription
nullDefault. 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 | DataViewUse a binary buffer as input.
ResponseUse the response body as input.
RequestUse the request body as input.
ReadableStreamUse a readable stream as input.
BlobUse a blob as input.
numberRead 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:
ValueDescription
"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.
numberWrite 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:
parent.ts
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"):
child.ts
process.send("Hello from child");

process.on("message", message => {
  console.log("Received from parent:", message);
});

Serialization format

The serialization option controls how messages are encoded between processes:
ValueDescription
"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:
bun-node-ipc.js
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:
childProc.disconnect();

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.

Build docs developers (and LLMs) love