Utility process (window.Electron.utilityProcess)
Runs your own Node.js code as a separate, Electron-managed process (Electron.utilityProcess.fork()) — the modern replacement for spawning node yourself via child_process, structured postMessage/onMessage like a Web Worker on the Node.js side. Source: src/main/utility-process.ts, channel prefix utilityProcess:*. Useful for offloading heavy or crash-prone work (big computations, image processing, native addon calls) so it can't freeze or take down the main process.
Opt-in per task. The renderer can only start a task declared by alias in processes.utilityProcesses — never an arbitrary module path. With nothing configured, every start() call rejects NOT_CONFIGURED, so this module is safe to leave enabled by default. See Processes configuration for the config shape and the externalCommands vs utilityProcesses mental model.
Methods
| Method | Returns | Notes |
|---|---|---|
start(alias: string, args?: string[]) |
Promise<{ id: string; pid: number | null }> |
Throws NOT_CONFIGURED if alias isn't declared. args are appended after the task's own configured args. |
postMessage(id: string, message: unknown) |
Promise<void> |
Throws VALIDATION if id isn't a currently-running task. |
kill(id: string) |
Promise<boolean> |
Resolves false when id is unknown (already exited or never existed). |
onMessage(callback: (event: { id: string; message: unknown }) => void) |
() => void (unsubscribe) |
Delivers every task's postMessage() calls back to main. |
onExit(callback: (event: { id: string; code: number }) => void) |
() => void (unsubscribe) |
modulePath (in electron.config.ts) resolves against resources/app/ in a packaged build — ship the file there via extraResources in electron-builder.config.ts. See Deployment: shipping extra resources.
Example
// electron.config.ts
processes: {
utilityProcesses: {
heavyMath: { modulePath: 'workers/heavy-math.js' },
},
},
// workers/heavy-math.js — plain Node.js, no @devioarts/electron import.
// Talks to the main process via Electron's own parentPort, not process.send().
process.parentPort.on('message', (event) => {
const { type, payload } = event.data;
if (type === 'sumOfSquares') {
let total = 0;
for (let i = 0; i < payload.n; i++) total += i * i;
process.parentPort.postMessage({ type: 'result', total });
}
});
// renderer
const { id } = await window.Electron.utilityProcess.start('heavyMath');
const unsubscribe = window.Electron.utilityProcess.onMessage(({ id: msgId, message }) => {
if (msgId !== id) return;
console.log('Result:', message.total);
unsubscribe();
});
await window.Electron.utilityProcess.postMessage(id, { type: 'sumOfSquares', payload: { n: 100_000_000 } });
// later:
await window.Electron.utilityProcess.kill(id);
.safe
Partial. start, postMessage, kill are wrapped — window.Electron.safe.utilityProcess.* resolves { ok: true, data } / { ok: false, error } instead of throwing. onMessage()/onExit() are the same functions on both surfaces (event subscriptions, never throw).