The
Worker
API is still experimental (particularly for terminating workers). We are actively
working on improving this.Worker
lets you start and communicate with a new JavaScript instance running on a separate thread while sharing I/O resources with the main thread.
Bun implements a minimal version of the Web Workers API with extensions that make it work better for server-side use cases. Like the rest of Bun, Worker
in Bun support CommonJS, ES Modules, TypeScript, JSX, TSX and more out of the box. No extra build steps are necessary.
Creating a Worker
Like in browsers, Worker
is a global. Use it to create a new worker thread.
From the main thread
Worker thread
worker.ts
self
, add this line to the top of your worker file.
import
and export
syntax in your worker code. Unlike in browsers, there’s no need to specify {type: "module"}
to use ES Modules.
To simplify error handling, the initial script to load is resolved at the time new Worker(url)
is called.
Worker
is resolved relative to the project root (like typing bun ./path/to/file.js
).
preload
- load modules before the worker starts
You can pass an array of module specifiers to the preload
option to load modules before the worker starts. This is useful when you want to ensure some code is always loaded before the application starts, like loading OpenTelemetry, Sentry, DataDog, etc.
--preload
CLI argument, the preload
option is processed before the worker starts.
You can also pass a single string to the preload
option:
blob:
URLs
As of Bun v1.1.13, you can also pass a blob:
URL to Worker
. This is useful for creating workers from strings or other sources.
blob:
URLs support TypeScript, JSX, and other file types out of the box. You can communicate it should be loaded via typescript either via type
or by passing a filename
to the File
constructor.
"open"
The "open"
event is emitted when a worker is created and ready to receive messages. This can be used to send an initial message to a worker once it’s ready. (This event does not exist in browsers.)
"open"
event to send messages.
Messages with postMessage
To send messages, use worker.postMessage
and self.postMessage
. This leverages the HTML Structured Clone Algorithm.
Performance optimizations
Bun includes optimized fast paths forpostMessage
to dramatically improve performance for common data types:
String fast path - When posting pure string values, Bun bypasses the structured clone algorithm entirely, achieving significant performance gains with no serialization overhead.
Simple object fast path - For plain objects containing only primitive values (strings, numbers, booleans, null, undefined), Bun uses an optimized serialization path that stores properties directly without full structured cloning.
The simple object fast path activates when the object:
- Is a plain object with no prototype chain modifications
- Contains only enumerable, configurable data properties
- Has no indexed properties or getter/setter methods
- All property values are primitives or strings
postMessage
performs 2-241x faster because the message length no longer has a meaningful impact on performance.
Bun (with fast paths):
message
event handler on the worker and main thread.
Terminating a worker
AWorker
instance terminates automatically once it’s event loop has no work left to do. Attaching a "message"
listener on the global or any MessagePort
s will keep the event loop alive. To forcefully terminate a Worker
, call worker.terminate()
.
process.exit()
A worker can terminate itself with process.exit()
. This does not terminate the main process. Like in Node.js, process.on('beforeExit', callback)
and process.on('exit', callback)
are emitted on the worker thread (and not on the main thread), and the exit code is passed to the "close"
event.
"close"
The "close"
event is emitted when a worker has been terminated. It can take some time for the worker to actually terminate, so this event is emitted when the worker has been marked as terminated. The CloseEvent
will contain the exit code passed to process.exit()
, or 0 if closed for other reasons.
Managing lifetime
By default, an activeWorker
will keep the main (spawning) process alive, so async tasks like setTimeout
and promises will keep the process alive. Attaching message
listeners will also keep the Worker
alive.
worker.unref()
To stop a running worker from keeping the process alive, call worker.unref()
. This decouples the lifetime of the worker to the lifetime of the main process, and is equivalent to what Node.js’ worker_threads
does.
worker.unref()
is not available in browsers.
worker.ref()
To keep the process alive until the Worker
terminates, call worker.ref()
. A ref’d worker is the default behavior, and still needs something going on in the event loop (such as a "message"
listener) for the worker to continue running.
options
object to Worker
:
worker.ref()
is not available in browsers.
Memory usage with smol
JavaScript instances can use a lot of memory. Bun’s Worker
supports a smol
mode that reduces memory usage, at a cost of performance. To enable smol
mode, pass smol: true
to the options
object in the Worker
constructor.
What does `smol` mode actually do?
What does `smol` mode actually do?
Setting
smol: true
sets JSC::HeapSize
to be Small
instead of the default Large
.Environment Data
Share data between the main thread and workers usingsetEnvironmentData()
and getEnvironmentData()
.
Worker Events
Listen for worker creation events usingprocess.emit()
:
Bun.isMainThread
You can check if you’re in the main thread by checking Bun.isMainThread
.