Skip to content

When to Use This Library

This library is most useful when you want tRPC’s router and client model across Electron process boundaries, especially when more than one renderer or utility process is involved.

With hand-written Electron IPC, each procedure usually needs a channel name, preload wrapper, renderer function, input validation, result typing, error mapping, and event cleanup.

// main
ipcMain.handle('greet', async (_event, input: { name: string }) => {
return `Hello, ${input.name}!`;
});
// preload and renderer wrappers are still needed to expose this safely.

With this package, the procedure lives in your tRPC router and the renderer uses a typed tRPC client.

// router
const appRouter = t.router({
greet: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => `Hello, ${input.name}!`),
});
// renderer
const result = await trpc.greet.query({ name: 'World' });

| Need | Why this package helps | |---|---| | Shared procedure types | Router inference flows into renderer and main-side clients | | Runtime validation | Use the same tRPC validators and middleware you already use | | MessagePort transport | Use a transferred MessagePort as the tRPC transport instead of building custom IPC wrappers | | Utility processes | mainPortLink(), createParentPortHandler(), and createPortBroker() cover main-to-utility and renderer-to-utility flows |

Plain ipcMain.handle() and ipcRenderer.invoke() may be simpler when your app has only a few calls, no shared tRPC router, and no need for utility-process topologies or subscription streams.

Choose electron-messageport-trpc when IPC has become part of your app’s typed API surface rather than a handful of one-off calls.