workerData
Holds a clone of the data passed to this thread's Worker constructor.
worker_threads.workerData- Added in Node.js v10.5.0
Return value
<any>A clone of the value given as the workerData option to this thread's Worker constructor; null on the main thread, and undefined inside a Worker that was created without a workerData option.
Errors
Nothing on read. The Worker constructor in the parent throws a DataCloneError if the supplied value cannot be structured-cloned, for example because it contains a function.
workerData is the thread's startup payload: whatever was given as the workerData option to new Worker(), copied across with the same structured clone algorithm postMessage() uses. It is available synchronously the moment the worker's script begins, which is what makes it different from a message — no listener, no first-tick race.
When to reach for it
Configuration and immutable input: a file path, a chunk range, a compiled ruleset, a SharedArrayBuffer the thread will operate on. It is the natural way to tell thread number 3 that it owns rows 3000 to 3999.
What trips people up
- It is a copy. Mutating
workerDatainside the thread changes nothing in the parent, and later changes in the parent are invisible to the thread. For genuinely shared state you need aSharedArrayBuffer, which is passed through by reference rather than copied. - Functions, class prototypes, promises and closures do not clone. Passing a callback throws a
DataCloneErrorin the parent, at thenew Worker()call. - Large objects are serialised twice, once out and once in. If you are moving megabytes, pass an
ArrayBufferintransferListinstead. - A
MessagePortinsideworkerDatamust also appear intransferListor the constructor throws.
Example
const { Worker, isMainThread, parentPort, workerData } = require('node:worker_threads');
if (isMainThread) {
console.log('workerData on main thread:', workerData);
const worker = new Worker(__filename, {
workerData: { start: 1, end: 5, label: 'squares' },
});
worker.on('message', (msg) => console.log(msg.label, msg.values));
} else {
const { start, end, label } = workerData;
const values = [];
for (let i = start; i <= end; i++) values.push(i * i);
parentPort.postMessage({ label, values });
}
// Prints:
// workerData on main thread: null
// squares [ 1, 4, 9, 16, 25 ]When to use it
- Tell each pool thread which slice of a dataset it owns
- Pass a file path, chunk offset and byte length for parallel file processing
- Hand a thread a SharedArrayBuffer to write results into
- Ship a compiled configuration or ruleset into a thread at startup
Frequently asked questions
How do I pass arguments to a worker thread?
Give the value as the workerData option to new Worker(), and read it from workerData inside the thread's script. It arrives synchronously before your code runs, so unlike a posted message there is no listener to set up and no startup race to handle.
Why is workerData undefined or null?
Because the code is running on the main thread, or the Worker was created without a workerData option. Check isMainThread first if the same file is used as both entry point and worker body.
Can a worker modify workerData so the parent sees the change?
No — it is a structured clone, so mutations stay inside the thread. Use a SharedArrayBuffer, which is passed by reference rather than copied, together with Atomics for anything both sides must read and write.
More examples
Splitting an array across four threads
Each thread gets its own slice at construction time, so no coordination messages are needed. The results are sorted by index before printing because threads finish in an unpredictable order — relying on Promise.all's positional ordering is safe here, but the explicit sort documents why the output is stable.
// main.js
import { Worker } from 'node:worker_threads';
const items = Array.from({ length: 400 }, (_, i) => i + 1);
const THREADS = 4;
const size = items.length / THREADS;
const jobs = Array.from({ length: THREADS }, (_, i) => {
const slice = items.slice(i * size, (i + 1) * size);
const worker = new Worker('./sum-slice.js', {
workerData: { index: i, slice },
name: `sum-${i}`,
});
return new Promise((resolve, reject) => {
worker.on('message', resolve);
worker.on('error', reject);
});
});
const partials = await Promise.all(jobs);
partials.sort((a, b) => a.index - b.index);
console.log('partials:', partials.map((p) => p.total));
console.log('total:', partials.reduce((a, p) => a + p.total, 0));
// sum-slice.js
import { parentPort, workerData } from 'node:worker_threads';
const { index, slice } = workerData;
parentPort.postMessage({ index, total: slice.reduce((a, b) => a + b, 0) });
// Prints:
// partials: [ 5050, 15050, 25050, 35050 ]
// total: 80200Sharing memory instead of copying it
A SharedArrayBuffer placed in workerData is not cloned — both threads map the same memory, so the worker's writes are visible in the parent without any postMessage. This is the only way to share mutable state between threads, and it is why Atomics exists: ordinary reads and writes across threads have no ordering guarantees. A plain ArrayBuffer here would have been copied instead, and the parent's array would still read 10, 20, 30, 40.
// main.js
const { Worker } = require('node:worker_threads');
const shared = new SharedArrayBuffer(4 * Int32Array.BYTES_PER_ELEMENT);
const view = new Int32Array(shared);
view.set([10, 20, 30, 40]);
const worker = new Worker('./double.js', { workerData: { shared } });
worker.on('exit', () => {
// The parent sees the worker's writes: no message carried them back.
console.log('after worker:', Array.from(view));
});
// double.js
const { workerData } = require('node:worker_threads');
const view = new Int32Array(workerData.shared);
for (let i = 0; i < view.length; i++) {
Atomics.store(view, i, Atomics.load(view, i) * 2);
}
// Prints:
// after worker: [ 20, 40, 60, 80 ]
Discussion