Spawn child processes with Bun.spawn
or Bun.spawnSync
.
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 directory
env: { ...process.env, FOO: "bar" }, // specify environment variables
onExit(proc, exitCode, signalCode, error) {
// exit handler
},
});
proc.pid; // process ID of subprocess
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(
"https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js",
),
});
const text = await new Response(proc.stdout).text();
console.log(text); // "const input = "hello world".repeat(400); ..."
{% table %}
null
- Default. 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 | DataView
- Use a binary buffer as input.
Response
- Use the response
body
as input.
Request
- Use the request
body
as input.
number
- Read from the file with a given file descriptor.
{% /table %}
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
proc.stdin.write("hello");
// enqueue binary data
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));
// send buffered data
proc.stdin.flush();
// close the input stream
proc.stdin.end();
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
:
{% table %}
"pipe"
- Default for
stdout
. Pipe the output to aReadableStream
on the returnedSubprocess
object.
"inherit"
- Default for
stderr
. Inherit from the parent process.
Bun.file()
- Write to the specified file.
null
- Write to
/dev/null
.
number
- Write to the file with the given file descriptor.
{% /table %}
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.kill();
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"]);
proc.unref();
Bun supports direct inter-process communication channel between two bun
processes. To receive messages from a spawned Bun subprocess, specify an ipc
handler.
{%callout%}
Note — This API is only compatible with other bun
processes. Use process.execPath
to get a path to the currently running bun
executable.
{%/callout%}
const child = Bun.spawn(["bun", "child.ts"], {
ipc(message) {
/**
* The message received from the sub process
**/
},
});
The parent process can send messages to the subprocess using the .send()
method on the returned Subprocess
instance. A reference to the sending subprocess is also available as the second argument in the ipc
handler.
const childProc = Bun.spawn(["bun", "child.ts"], {
ipc(message, childProc) {
/**
* The message received from the sub process
**/
childProc.send("Respond to child")
},
});
childProc.send("I am your father"); // The parent can send messages to the child as well
Meanwhile the child process can send messages to its parent using with process.send()
and receive messages with process.on("message")
. This is the same API used for child_process.fork()
in Node.js.
process.send("Hello from child as string");
process.send({ message: "Hello from child as object" });
process.on("message", (message) => {
// print message from parent
console.log(message);
});
All messages are serialized using the JSC serialize
API, which allows for the same set of transferrable types supported by postMessage
and structuredClone
, including strings, typed arrays, streams, and objects.
// send a string
process.send("Hello from child as string");
// send an object
process.send({ message: "Hello from child as object" });
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.
- It contains a
success
property that indicates whether the process exited with a zero exit code. - The
stdout
andstderr
properties are instances ofBuffer
instead ofReadableStream
. - There is no
stdin
property. UseBun.spawn
to incrementally write to the subprocess's input stream.
const proc = Bun.spawnSync(["echo", "hello"]);
console.log(proc.stdout.toString());
// => "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.
{%callout%}
⚡️ Under the hood, Bun.spawn
and Bun.spawnSync
use posix_spawn(3)
.
{%/callout%}
Bun's spawnSync
spawns processes 60% faster than the Node.js child_process
module.
$ bun spawn.mjs
cpu: Apple M1 Max
runtime: bun 1.x (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
A simple reference of the Spawn API and types are shown below. The real types have complex generics to strongly type the Subprocess
streams with the options passed to Bun.spawn
and Bun.spawnSync
. For full details, find these types as defined bun.d.ts.
interface Bun {
spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
spawnSync(
command: string[],
options?: SpawnOptions.OptionsObject,
): SyncSubprocess;
spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
spawnSync(
options: { cmd: string[] } & SpawnOptions.OptionsObject,
): SyncSubprocess;
}
namespace SpawnOptions {
interface OptionsObject {
cwd?: string;
env?: Record<string, string>;
stdin?: SpawnOptions.Readable;
stdout?: SpawnOptions.Writable;
stderr?: SpawnOptions.Writable;
onExit?: (
proc: Subprocess,
exitCode: number | null,
signalCode: string | null,
error: Error | null,
) => void;
}
type Readable =
| "pipe"
| "inherit"
| "ignore"
| null // equivalent to "ignore"
| undefined // to use default
| BunFile
| ArrayBufferView
| number;
type Writable =
| "pipe"
| "inherit"
| "ignore"
| null // equivalent to "ignore"
| undefined // to use default
| BunFile
| ArrayBufferView
| number
| ReadableStream
| Blob
| Response
| Request;
}
interface Subprocess<Stdin, Stdout, Stderr> {
readonly pid: number;
// the exact stream types here are derived from the generic parameters
readonly stdin: number | ReadableStream | FileSink | undefined;
readonly stdout: number | ReadableStream | undefined;
readonly stderr: number | ReadableStream | undefined;
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<Stdout, Stderr> {
readonly pid: number;
readonly success: boolean;
// the exact buffer types here are derived from the generic parameters
readonly stdout: Buffer | undefined;
readonly stderr: Buffer | undefined;
}
type ReadableSubprocess = Subprocess<any, "pipe", "pipe">;
type WritableSubprocess = Subprocess<"pipe", any, any>;
type PipedSubprocess = Subprocess<"pipe", "pipe", "pipe">;
type NullSubprocess = Subprocess<null, null, null>;
type ReadableSyncSubprocess = SyncSubprocess<"pipe", "pipe">;
type NullSyncSubprocess = SyncSubprocess<null, null>;
type Signal =
| "SIGABRT"
| "SIGALRM"
| "SIGBUS"
| "SIGCHLD"
| "SIGCONT"
| "SIGFPE"
| "SIGHUP"
| "SIGILL"
| "SIGINT"
| "SIGIO"
| "SIGIOT"
| "SIGKILL"
| "SIGPIPE"
| "SIGPOLL"
| "SIGPROF"
| "SIGPWR"
| "SIGQUIT"
| "SIGSEGV"
| "SIGSTKFLT"
| "SIGSTOP"
| "SIGSYS"
| "SIGTERM"
| "SIGTRAP"
| "SIGTSTP"
| "SIGTTIN"
| "SIGTTOU"
| "SIGUNUSED"
| "SIGURG"
| "SIGUSR1"
| "SIGUSR2"
| "SIGVTALRM"
| "SIGWINCH"
| "SIGXCPU"
| "SIGXFSZ"
| "SIGBREAK"
| "SIGLOST"
| "SIGINFO";