electron-ipc-handler
v0.1.0
Published
Type-safe Electron IPC handler with TC39 decorators, auto-registration, and code generation
Downloads
386
Maintainers
Readme
// main process — define handlers
@ipcClass('users')
export class UserIpcHandler {
@ipcMethod()
getAll() { return db.users.findMany(); }
}
// renderer — call with full type safety
const users = await ipc.users.getAll(); // User[]Why?
| Problem | Solution |
|---------|----------|
| Channel names are stringly-typed — typos cause silent failures | Auto-generated IpcApi type from your handlers |
| No shared types between main and renderer | End-to-end type inference, zero manual sync |
| Bundler needs boilerplate for electron + Node.js externals | Plugins auto-configure everything |
Features
| | |
|---|---|
| Type-safe | End-to-end types from main to renderer |
| Auto codegen | Scans handlers, emits IpcApi type on every build |
| Two styles | Class-based (@ipcClass) or function-based (ipcHandler) |
| Interceptors | Koa-style middleware for logging, auth, validation |
| Context | getIpcContext() anywhere via AsyncLocalStorage |
| DI ready | resolver option for tsyringe, inversify, or any container |
| Bundler plugins | Vite, webpack, Rollup, esbuild |
| Zero config | Auto-configures electron + Node.js built-in externals |
Install
npm install electron-ipc-handlerPeer dependency:
electron >= 13.0.0
Quick Start
Project Structure
src/
├── main/
│ ├── main.ts
│ └── ipc/
│ ├── user.ipc.ts ← class-based handler
│ ├── system.ipc.ts ← function-based handler
│ └── ipc.gen.ts ← auto-generated
├── preload/
│ └── preload.ts
└── renderer/
└── ipc/
└── ipc.client.ts ← auto-generatedStep 1 — Define handlers
// user.ipc.ts
import { ipcClass, ipcMethod }
from 'electron-ipc-handler';
@ipcClass('users')
export class UserIpcHandler {
@ipcMethod()
getAll() {
return [{ id: 1, name: 'Alice' }];
}
@ipcMethod()
getById(id: number) {
return users.find(u => u.id === id);
}
}// system.ipc.ts
import { ipcHandler }
from 'electron-ipc-handler';
export const ping = ipcHandler(
'system:ping',
(msg: string) => `pong: ${msg}`
);
export const version = ipcHandler(
'system:version',
() => '1.0.0'
);Both styles coexist in the same directory. The plugin detects them by decorator / function usage, not by file name.
Step 2 — Configure your bundler
// vite.config.ts (main process)
import { electronIpcPlugin } from 'electron-ipc-handler/vite';
export default defineConfig({
plugins: [
electronIpcPlugin({
dirs: ['src/main/ipc'],
output: 'src/main/ipc/ipc.gen.ts',
client: 'src/renderer/ipc/ipc.client.ts',
}),
],
});Step 3 — Register in main process
// main.ts
import { registerIpcDecoratorHandler } from './ipc/ipc.gen';
app.whenReady().then(() => {
registerIpcDecoratorHandler();
createWindow();
});Step 4 — Preload
// preload.ts
import { setupPreload } from 'electron-ipc-handler/preload';
setupPreload();Step 5 — Use in renderer
The plugin auto-generates src/renderer/ipc/ipc.client.ts:
// auto-generated — src/renderer/ipc/ipc.client.ts
import { createIpcClient } from 'electron-ipc-handler';
import type { IpcApi } from '../../main/ipc/ipc.gen';
export const ipc = createIpcClient<IpcApi>();// anywhere in the renderer
import { ipc } from './ipc/ipc.client';
const users = await ipc.users.getAll(); // User[]
const pong = await ipc.system.ping('hello'); // stringThat's it. Full type safety, no channel strings in your app code.
Bundler Plugins
Every plugin does two things:
- Codegen — scans handler directories and generates the
IpcApitype file - Externals — auto-configures
electronand Node.js built-in externals
Vite
import { electronIpcPlugin } from 'electron-ipc-handler/vite';
export default defineConfig({
plugins: [
electronIpcPlugin({
dirs: ['src/main/ipc'],
output: 'src/main/ipc/ipc.gen.ts',
}),
],
});webpack
import { electronIpcPlugin } from 'electron-ipc-handler/webpack';
export default {
plugins: [
electronIpcPlugin({
dirs: ['src/main/ipc'],
output: 'src/main/ipc/ipc.gen.ts',
}),
],
};Note: If you use
ts-loader, set"target": "ES2022"in yourtsconfig.json. With"ESNext", TypeScript preserves TC39 decorator syntax (@) in the output, which webpack cannot parse.
Rollup
import { electronIpcPlugin } from 'electron-ipc-handler/rollup';
export default {
plugins: [
...electronIpcPlugin({
dirs: ['src/main/ipc'],
output: 'src/main/ipc/ipc.gen.ts',
}),
],
};esbuild
import { electronIpcPlugin } from 'electron-ipc-handler/esbuild';
esbuild.build({
plugins: [
electronIpcPlugin({
dirs: ['src/main/ipc'],
output: 'src/main/ipc/ipc.gen.ts',
}),
],
});Advanced (unplugin)
For custom setups, use the raw unplugin factory. This does not auto-configure externals.
import { ipcDecoratorPlugin } from 'electron-ipc-handler/unplugin';
const plugin = ipcDecoratorPlugin.vite({
dirs: ['src/main/ipc'],
output: 'src/main/ipc/ipc.gen.ts',
});Plugin Options
| Option | Type | Default | Description |
|----------|------------|---------|-------------|
| dirs | string[] | — | Directories to scan for handler files |
| output | string | — | Output path for the generated type file |
| client | boolean \| string | true | true = client next to gen file, string = custom output path, false = skip |
All paths are relative to the project root. Generated files (*.gen.ts, ipc.client.ts) are safe to place inside dirs — the plugin only picks up files containing @ipcClass or ipcHandler calls.
API Reference
Decorators
@ipcClass(namespace)
Registers all @ipcMethod() methods under the given namespace.
@ipcClass('users')
export class UserIpcHandler { ... }
// → channels: users:getAll, users:getById, ...@ipcMethod(channel?)
Uses the method name as the channel if omitted.
@ipcMethod() // channel = method name
getAll() { ... }
@ipcMethod('find') // channel = 'find'
getById(id: number) { ... }Functions
ipcHandler(channel, fn)
Standalone function handler. Channel format: namespace:method.
export const ping = ipcHandler('system:ping', (msg: string) => `pong: ${msg}`);registerIpcDecoratorHandler(options?)
Connects all handlers to ipcMain.handle. Call once after app is ready.
registerIpcDecoratorHandler();
// With options
registerIpcDecoratorHandler({
resolver: (Target) => container.resolve(Target),
interceptors: [loggingInterceptor],
});| Option | Type | Description |
|--------|------|-------------|
| resolver | (target: Constructor) => instance | Class instance factory (DI) |
| interceptors | IpcInterceptor[] | Koa-style middleware array |
getIpcContext()
Returns the current IPC request context via AsyncLocalStorage. Works anywhere in the call chain.
const { channel, sender, args, event } = getIpcContext();| Field | Type | Description |
|-------|------|-------------|
| channel | string | e.g., 'users:getAll' |
| args | unknown[] | Invocation arguments |
| sender | WebContents | Requesting window |
| event | IpcMainInvokeEvent | Raw IPC event |
createIpcClient<T>()
Type-safe IPC client for the renderer.
const ipc = createIpcClient<IpcApi>();
// ipc.users.getAll() → ipcRenderer.invoke('users:getAll')Type Utilities
InferApi<T>
Extracts an API type map from handler classes:
type Api = InferApi<{ users: UserIpcHandler }>;
// → { users: { getAll(): Promise<User[]> } }ExtractIpcFn<T>
Extracts the function signature from an ipcHandler:
type PingFn = ExtractIpcFn<typeof ping>;
// → (msg: string) => Promise<string>Interceptors
Koa-style middleware. Runs inside AsyncLocalStorage context.
import type { IpcInterceptor } from 'electron-ipc-handler';
const logger: IpcInterceptor = async (ctx, next) => {
const start = Date.now();
console.log(`→ ${ctx.channel}`, ctx.args);
const result = await next();
console.log(`← ${ctx.channel} ${Date.now() - start}ms`);
return result;
};
const auth: IpcInterceptor = async (ctx, next) => {
if (!isAuthorized(ctx.sender.id)) throw new Error('Unauthorized');
return next();
};
registerIpcDecoratorHandler({
interceptors: [logger, auth],
});Preload
One-liner setup:
import { setupPreload } from 'electron-ipc-handler/preload';
setupPreload();Or manual setup if you need custom configuration:
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
invoke: (channel: string, ...args: unknown[]) =>
ipcRenderer.invoke(channel, ...args),
},
});Examples
| Example | Bundler | Description |
|---------|---------|-------------|
| examples/vite | Vite | Full working Electron app |
| examples/forge-vite | Electron Forge + Vite | Forge integration |
| examples/forge-webpack | Electron Forge + webpack | Forge integration |
License
MIT
