Docs

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).

Last updated on July 18, 2026