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.
What You Avoid Writing
Section titled “What You Avoid Writing”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.
// mainipcMain.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.
// routerconst appRouter = t.router({ greet: t.procedure .input(z.object({ name: z.string() })) .query(({ input }) => `Hello, ${input.name}!`),});
// rendererconst result = await trpc.greet.query({ name: 'World' });Where It Pays Off
Section titled “Where It Pays Off”| 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 |
When Plain IPC Is Enough
Section titled “When Plain IPC Is Enough”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.