Spawn child processes with Bun.spawn or Bun.spawnSync.

Spawn a process

Provide a command as an array of strings. The result of Bun.spawn() is a Bun.Subprocess object.

Bun.spawn(["echo", "hello"]);

The second argument to Bun.spawn is a parameters object that can be used to configure the subprocess.

const proc = Bun.spawn(["echo", "hello"], {
  cwd: "./path/to/subdir", // specify a working direcory
  env: { ...process.env, FOO: "bar" }, // specify environment variables
  onExit(proc, exitCode, signalCode, error) {
    // exit handler

proc.pid; // process ID of subprocess

Input stream

By default, the input stream of the subprocess is undefined; it can be configured with the stdin parameter.

const proc = Bun.spawn(["cat"], {
  stdin: await fetch(

const text = await new Response(proc.stdout).text();
console.log(text); // "const input = "hello world".repeat(400); ..."
nullDefault. Provide no input to the subprocess
"pipe"Return a FileSink for fast incremental writing
"inherit"Inherit the stdin of the parent process
Bun.file()Read from the specified file.
TypedArray | DataViewUse a binary buffer as input.
ResponseUse the response body as input.
RequestUse the request body as input.
numberRead from the file with a given file descriptor.

The "pipe" option lets incrementally write to the subprocess's input stream from the parent process.

const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // return a FileSink for writing

// enqueue string data

// enqueue binary data
const enc = new TextEncoder();
proc.stdin!.write(enc.encode(" world!"));

// send buffered data

// close the input stream

Output streams

You can read results from the subprocess via the stdout and stderr properties. By default these are instances of ReadableStream.

const proc = Bun.spawn(["echo", "hello"]);
const text = await new Response(proc.stdout).text();
console.log(text); // => "hello"

Configure the output stream by passing one of the following values to stdout/stderr:

"pipe"Default for stdout. Pipe the output to a ReadableStream on the returned Subprocess object.
"inherit"Default for stderr. Inherit from the parent process.
Bun.file()Write to the specified file.
nullWrite to /dev/null.
numberWrite to the file with the given file descriptor.

Exit handling

Use the onExit callback to listen for the process exiting or being killed.

const proc = Bun.spawn(["echo", "hello"], {
  onExit(proc, exitCode, signalCode, error) {
    // exit handler

For convenience, the exited property is a Promise that resolves when the process exits.

const proc = Bun.spawn(["echo", "hello"]);

await proc.exited; // resolves when process exit
proc.killed; // boolean — was the process killed?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...

To kill a process:

const proc = Bun.spawn(["echo", "hello"]);
proc.killed; // true

proc.kill(); // specify an exit code

The parent bun process will not terminate until all child processes have exited. Use proc.unref() to detach the child process from the parent.

const proc = Bun.spawn(["echo", "hello"]);

Blocking API

Bun provides a synchronous equivalent of Bun.spawn called Bun.spawnSync. This is a blocking API that supports the same inputs and parameters as Bun.spawn. It returns a SyncSubprocess object, which differs from Subprocess in a few ways.

  1. It contains a success property that indicates whether the process exited with a zero exit code.
  2. The stdout and stderr properties are instances of Buffer instead of ReadableStream.
  3. There is no stdin property. Use Bun.spawn to incrementally write to the subprocess's input stream.
const proc = Bun.spawnSync(["echo", "hello"]);

// => "hello\n"

As a rule of thumb, the asynchronous Bun.spawn API is better for HTTP servers and apps, and Bun.spawnSync is better for building command-line tools.


⚡️ Under the hood, Bun.spawn and Bun.spawnSync use posix_spawn(3).

Bun's spawnSync spawns processes 60% faster than the Node.js child_process module.

bun spawn.mjs
cpu: Apple M1 Max
runtime: bun 0.2.0 (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi  888.14 µs/iter    (821.83 µs … 1.2 ms) 905.92 µs      1 ms   1.03 ms
node spawn.node.mjs
cpu: Apple M1 Max
runtime: node v18.9.1 (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi    1.47 ms/iter     (1.14 ms … 2.64 ms)   1.57 ms   2.37 ms   2.52 ms


interface Bun {
  spawn(command: string[], options?: SpawnOptions): Subprocess;
  spawnSync(command: string[], options?: SpawnOptions): SyncSubprocess;

interface SpawnOptions {
  cwd?: string;
  env?: Record<string, string>;
    | "pipe"
    | "inherit"
    | "ignore"
    | ReadableStream
    | BunFile
    | Blob
    | Response
    | Request
    | number
    | null;
    | "pipe"
    | "inherit"
    | "ignore"
    | BunFile
    | TypedArray
    | DataView
    | null;
    | "pipe"
    | "inherit"
    | "ignore"
    | BunFile
    | TypedArray
    | DataView
    | null;
  onExit?: (
    proc: Subprocess,
    exitCode: number | null,
    signalCode: string | null,
    error: Error | null,
  ) => void;

interface Subprocess {
  readonly pid: number;
  readonly stdin?: number | ReadableStream | FileSink;
  readonly stdout?: number | ReadableStream;
  readonly stderr?: number | ReadableStream;

  readonly exited: Promise<number>;

  readonly exitCode: number | undefined;
  readonly signalCode: Signal | null;
  readonly killed: boolean;

  ref(): void;
  unref(): void;
  kill(code?: number): void;

interface SyncSubprocess {
  readonly pid: number;
  readonly success: boolean;
  readonly stdout: Buffer;
  readonly stderr: Buffer;

type Signal =
  | "SIGBUS"
  | "SIGFPE"
  | "SIGHUP"
  | "SIGILL"
  | "SIGINT"
  | "SIGIO"
  | "SIGIOT"
  | "SIGPWR"
  | "SIGSYS"
  | "SIGURG"
  | "SIGUSR1"
  | "SIGUSR2"
  | "SIGINFO";