function

spawn

function spawn<In extends Writable = 'ignore', Out extends Readable = 'pipe', Err extends Readable = 'inherit'>(
options: SpawnOptions<In, Out, Err> & { cmd: string[] }
): Subprocess<In, Out, Err>;

Spawn a new process

function spawn<In extends Writable = 'ignore', Out extends Readable = 'pipe', Err extends Readable = 'inherit'>(
cmds: string[],
options?: SpawnOptions<In, Out, Err>
): Subprocess<In, Out, Err>;

Spawn a new process

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

Internally, this uses posix_spawn(2)

@param cmds

The command to run

The first argument is resolved to an absolute executable path. It must be a file, not a directory.

If you explicitly set PATH in env, that PATH is used to resolve the executable instead of the default PATH.

To check if the command exists before running it, use Bun.which(bin).

Referenced types

interface SpawnOptions<In extends Writable, Out extends Readable, Err extends Readable>

  • argv0?: string

    Path to the executable to run in the subprocess.

    Use this to wrap another application or to simulate a symlink.

  • cgroup?: string | number

    Start the child process inside this control group.

    Pass the path of an existing cgroup directory (e.g. "/sys/fs/cgroup/my-jobs"), or an open file descriptor for one. The child joins it before it begins executing, so resource limits configured on the cgroup (memory.max, pids.max, …) apply from its first instruction and to everything it spawns in turn. Works with both cgroup v1 and v2 hierarchies.

    Bun does not create or configure the cgroup; do that with node:fs beforehand.

    Linux only; ignored on other platforms. On Linux, the spawn fails if the cgroup cannot be joined (e.g. the directory does not exist).

    import { mkdirSync, writeFileSync } from "node:fs";
    const dir = "/sys/fs/cgroup/build-jobs";
    mkdirSync(dir, { recursive: true });
    writeFileSync(dir + "/memory.max", String(2 * 1024 ** 3));
    Bun.spawn({ cmd: ["make"], cgroup: dir });
  • cwd?: string

    The current working directory of the process

    Defaults to process.cwd()

  • detached?: boolean

    Run the child in a separate process group, detached from the parent.

    • POSIX: calls setsid() so the child starts a new session and becomes the process group leader. It can outlive the parent and receive signals independently of the parent’s terminal/process group.
    • Windows: sets UV_PROCESS_DETACHED, allowing the child to outlive the parent and receive signals independently.

    Note: stdio may keep the parent process alive. Pass stdio: ["ignore", "ignore", "ignore"] to the spawn constructor to prevent this.

  • env?: Record<string, undefined | string>

    The environment variables of the process

    Defaults to process.env as it was when the current Bun process launched.

    Changes to process.env at runtime won't automatically be reflected in the default value. For that, you can pass process.env explicitly.

  • gid?: number

    Sets the group identity of the child process (see setgid(2)).

    POSIX only. On Windows the spawn fails with ENOTSUP.

  • killSignal?: string | number

    The signal to use when killing the process after a timeout, when the AbortSignal is aborted, or when the process goes over the maxBuffer limit.

    // Kill the process with SIGKILL after 5 seconds
    const subprocess = Bun.spawn({
      cmd: ["sleep", "10"],
      timeout: 5000,
      killSignal: "SIGKILL",
    });
  • lazy?: boolean

    If true, the stdout and stderr pipes don't automatically start reading data. Reading begins only when you access the stdout or stderr properties.

    This can improve performance when you don't need to read output immediately.

    const subprocess = Bun.spawn({
      cmd: ["echo", "hello"],
      lazy: true, // Don't start reading stdout until accessed
    });
    // stdout reading hasn't started yet
    await subprocess.stdout.text(); // Now reading starts
  • maxBuffer?: number

    The maximum number of bytes the process may output. If the process goes over this limit, it is killed with signal killSignal (defaults to SIGTERM).

  • serialization?: 'json' | 'advanced'

    The serialization format to use for IPC messages. Defaults to "advanced".

    To communicate with Node.js processes, use "json".

    When ipc is not specified, this is ignored.

  • signal?: AbortSignal

    An AbortSignal that kills the subprocess when aborted.

    Use this to abort the subprocess when another part of the program is aborted, such as a fetch.

    If the signal is already aborted when spawn is called, no process is created and an AbortError (with cause set to signal.reason) is thrown synchronously.

    If the signal is aborted after the process starts, the process is killed with the signal specified by killSignal (defaults to SIGTERM).

    const controller = new AbortController();
    const { signal } = controller;
    const start = performance.now();
    const subprocess = Bun.spawn({
     cmd: ["sleep", "100"],
     signal,
    });
    await Bun.sleep(1);
    controller.abort();
    await subprocess.exited;
    const end = performance.now();
    console.log(end - start); // 1ms instead of 101ms
  • stderr?: Err

    The file descriptor for the standard error. It may be:

    • "pipe", undefined: The process has a ReadableStream for standard output/error
    • "ignore", null: The process has no standard output/error
    • "inherit": The process inherits the standard output/error of the current process
    • ArrayBufferView: The process writes to the preallocated buffer. Not implemented.
    • number: The process writes to the file descriptor
  • stdin?: In

    The file descriptor for the standard input. It may be:

    • "ignore", null, undefined: The process has no standard input
    • "pipe": The process has a new FileSink for standard input
    • "inherit": The process inherits the standard input of the current process
    • ArrayBufferView, Blob: The process reads from the buffer
    • number: The process reads from the file descriptor
  • stdio?: [In, Out, Err, ...Readable | 'socket-fd'[]]

    The standard file descriptors of the process, in the form [stdin, stdout, stderr]. This overrides the stdin, stdout, and stderr properties.

    For stdin you may pass:

    • "ignore", null, undefined: The process has no standard input (default)
    • "pipe": The process has a new FileSink for standard input
    • "inherit": The process inherits the standard input of the current process
    • ArrayBufferView, Blob, Bun.file(), Response, Request: The process reads from buffer/stream.
    • number: The process reads from the file descriptor

    For stdout and stderr you may pass:

    • "pipe", undefined: The process has a ReadableStream for standard output/error
    • "ignore", null: The process has no standard output/error
    • "inherit": The process inherits the standard output/error of the current process
    • ArrayBufferView: The process writes to the preallocated buffer. Not implemented.
    • number: The process writes to the file descriptor

    At indices >= 3, "socket-fd" (POSIX only) is also accepted: creates a socketpair like "pipe", but the parent-end fd exposed via Subprocess.stdio is owned by the caller and is never closed by the subprocess. Use this when you wrap the fd in something that will close it itself (e.g. net.connect({fd})). On Windows it behaves the same as "pipe".

  • stdout?: Out

    The file descriptor for the standard output. It may be:

    • "pipe", undefined: The process has a ReadableStream for standard output/error
    • "ignore", null: The process has no standard output/error
    • "inherit": The process inherits the standard output/error of the current process
    • ArrayBufferView: The process writes to the preallocated buffer. Not implemented.
    • number: The process writes to the file descriptor
  • terminal?: TerminalOptions | Terminal

    Spawn the subprocess with a pseudo-terminal (PTY) attached.

    When this option is provided:

    • stdin, stdout, and stderr are all connected to the terminal
    • The subprocess sees itself running in a real terminal (isTTY = true)
    • Access the terminal via subprocess.terminal
    • subprocess.stdin, subprocess.stdout, subprocess.stderr return null

    Only available on POSIX systems (Linux, macOS).

    const proc = Bun.spawn(["bash"], {
      terminal: {
        cols: 80,
        rows: 24,
        data: (term, data) => console.log(data.toString()),
      },
    });
    
    proc.terminal.write("echo hello\n");
    await proc.exited;
    proc.terminal.close();

    You can also pass an existing Terminal object for reuse across multiple spawns:

    const terminal = new Bun.Terminal({ ... });
    const proc1 = Bun.spawn(["echo", "first"], { terminal });
    await proc1.exited;
    const proc2 = Bun.spawn(["echo", "second"], { terminal });
    await proc2.exited;
    terminal.close();
  • timeout?: number

    The maximum amount of time the process is allowed to run in milliseconds.

    If the timeout is reached, the process is killed with the signal specified by killSignal (defaults to SIGTERM).

    // Kill the process after 5 seconds
    const subprocess = Bun.spawn({
      cmd: ["sleep", "10"],
      timeout: 5000,
    });
    await subprocess.exited; // Will resolve after 5 seconds
  • uid?: number

    Sets the user identity of the child process (see setuid(2)).

    POSIX only. On Windows the spawn fails with ENOTSUP.

  • windowsHide?: boolean

    If true, the subprocess has a hidden window.

  • windowsVerbatimArguments?: boolean

    If true, no quoting or escaping of arguments is done on Windows.

  • message: any,
    subprocess: Subprocess<In, Out, Err>,
    handle?: unknown
    ): void;

    When specified, Bun opens an IPC channel to the subprocess. The passed callback is called for incoming messages, and subprocess.send can send messages to the subprocess. Messages are serialized using the JSC serialize API, which allows the same types that postMessage/structuredClone supports.

    The subprocess can send and receive messages with process.send and process.on("message"), respectively. This is the same API that Node.js exposes when child_process.fork() is used.

    This is only compatible with processes that are other bun instances.

    @param subprocess

    The Subprocess that received the message

  • onDisconnect(): void | Promise<void>;

    Called exactly once when the IPC channel between the parent and this subprocess is closed. After this runs, no further IPC messages will be delivered.

    When it fires:

    • The child called process.disconnect() or the parent called subprocess.disconnect().
    • The child exited for any reason (normal exit or due to a signal like SIGILL, SIGKILL, etc.).
    • The child replaced itself with a program that does not support Bun IPC.

    Notes:

    • This callback indicates that the pipe is closed; it is not an error by itself. Use onExit or Subprocess.exited to determine why the process ended.
    • It may occur before or after onExit depending on timing; do not rely on ordering. Typically, if you or the child call disconnect() first, this fires before onExit; if the process exits without an explicit disconnect, either may happen first.
    • Only runs when ipc is enabled and runs at most once per subprocess.
    • If the child becomes a zombie (exited but not yet reaped), the IPC is already closed, and this callback will fire (or may already have fired).
    const subprocess = spawn({
     cmd: ["echo", "hello"],
     ipc: (message) => console.log(message),
     onDisconnect: () => {
       console.log("IPC channel disconnected");
     },
    });
  • subprocess: Subprocess<In, Out, Err>,
    exitCode: null | number,
    signalCode: null | number,
    error?: ErrorLike
    ): void | Promise<void>;

    Callback that runs when the Subprocess exits

    This is called even if the process exits with a non-zero exit code.

    Warning: this may run before the Bun.spawn function returns.

    An alternative is await subprocess.exited.

    @param error

    If an error occurred in the call to waitpid2, this is the error.

    const subprocess = spawn({
     cmd: ["echo", "hello"],
     onExit: (subprocess, code) => {
       console.log(`Process exited with code ${code}`);
      },
    });

interface Subprocess<In extends SpawnOptions.Writable = SpawnOptions.Writable, Out extends SpawnOptions.Readable = SpawnOptions.Readable, Err extends SpawnOptions.Readable = SpawnOptions.Readable>

A process created by Bun.spawn.

The 3 optional type parameters correspond to the stdio array from the options object. Instead of specifying them, use one of these utility types:

  • ReadableSubprocess (any, pipe, pipe)
  • WritableSubprocess (pipe, any, any)
  • PipedSubprocess (pipe, pipe, pipe)
  • NullSubprocess (ignore, ignore, ignore)
  • readonly exitCode: null | number

    Synchronously get the exit code of the process

    null if the process hasn't exited yet

  • readonly exited: Promise<number>

    The exit code of the process

    The promise resolves when the process exits

  • readonly killed: boolean

    Whether the process has exited

  • readonly pid: number

    The process ID of the child process

    const { pid } = Bun.spawn({ cmd: ["echo", "hello"] });
    console.log(pid); // 1234
  • readonly readable: ReadableToIO<Out>

    The same value as Subprocess.stdout

    Exists for compatibility with ReadableStream.pipeThrough

  • readonly signalCode: null | Signals

    Synchronously get the signal code of the process

    null if the process never sent a signal code

    To receive signal code changes, use the onExit callback.

    If the signal code is unknown, this is the original signal code number, but that case should never happen in practice.

  • readonly stderr: ReadableToIO<Err>
  • readonly stdin: WritableToIO<In>
  • readonly stdio: [null, null, null, ...null | number[]]

    Extra file descriptors passed to the stdio option.

    Entries beyond index 2 are number for "pipe" and "socket-fd" slots and, on POSIX, for slots where a raw file descriptor was supplied (the same fd is returned). On POSIX, reading this property transfers ownership of any "pipe" fds to the caller, who is then responsible for closing them; the subprocess will not close them. "socket-fd" and raw-fd slots are likewise caller-owned. Other slots — including raw fds on Windows — are null.

  • readonly stdout: ReadableToIO<Out>
  • readonly terminal: undefined | Terminal

    The terminal attached to this subprocess, if spawned with the terminal option. undefined if no terminal was attached.

    When a terminal is attached, stdin, stdout, and stderr return null. Use terminal.write() and the data callback instead.

    const proc = Bun.spawn(["bash"], {
      terminal: { data: (term, data) => console.log(data.toString()) },
    });
    
    proc.terminal?.write("echo hello\n");
  • [Symbol.asyncDispose](): PromiseLike<void>;
  • disconnect(): void;

    Disconnect the IPC channel to the subprocess. This is only supported if the subprocess was created with the ipc option.

  • exitCode?: number | Signals
    ): void;

    Kill the process

    @param exitCode

    Exit code or signal to send to the process

  • ref(): void;

    Tell Bun to wait for this process to exit after you already called unref().

    By default, Bun waits for all subprocesses to exit before shutting down

  • Get the resource usage of the process, such as max RSS and CPU time

    Returns undefined until the process has exited

  • message: any
    ): void;

    Send a message to the subprocess. This is only supported if the subprocess was created with the ipc option, and is another instance of bun.

    Messages are serialized using the JSC serialize API, which allows for the same types that postMessage/structuredClone supports.

  • unref(): void;

    Tell Bun not to wait for this process to exit before shutting down.

    By default, Bun waits for all subprocesses to exit before shutting down.