Skip to main content

Migrate from Manual IPC

This guide shows how to move from manual ipcMain + custom preload wiring to controller-based IPC with generated renderer types.

Migration goals

  • Replace stringly-typed channels with controller methods
  • Remove duplicated preload wiring
  • Centralize API shape in main-process controller signatures

Step 1: Replace manual handlers with controller methods

Before:

ipcMain.handle("users.get_profile", async (_event, userId: string) => {
return userService.getProfile(userId);
});

After:

@IpcController("users")
class UsersController {
@IpcHandle("getProfile")
async getProfile(userId: string) {
return userService.getProfile(userId);
}
}

Step 2: Register controllers at startup

createIpcApp({
controllers: [UsersController],
resolver: {
resolve: (Controller) => new Controller(),
},
});

If you already use DI, adapt resolve to your container.

Step 3: Replace manual preload bridge with setupPreload

Before:

contextBridge.exposeInMainWorld("api", {
getProfile: (id: string) => ipcRenderer.invoke("users.get_profile", id),
});

(Using Electron's contextBridge and ipcRenderer.)

After:

import { setupPreload } from "@electron-ipc-bridge/core/preload";

void setupPreload(); // exposes window.ipc by default

Step 4: Update renderer calls

Before:

const profile = await window.api.getProfile(userId);

After:

const profile = await window.ipc.users.getProfile(userId);

Step 5: Validate behaviour and remove legacy code

  • Remove old ipcMain.handle/ipcMain.on registrations for migrated endpoints
  • Remove redundant preload bridge methods that are now generated
  • Keep reflect-metadata import at main entrypoint
  • Confirm renderer compiles against generated API types

Common migration pitfalls

  • Namespace collisions across controllers
  • Missing decorator/metadata TypeScript config
  • Preload path mismatch in packaged builds
  • Dynamic controller registration patterns that break plugin analysis

Suggested incremental rollout

  1. Migrate one domain controller (for example, users)
  2. Keep legacy channels for untouched domains
  3. Verify behaviour and typing in renderer
  4. Migrate remaining domains and remove legacy bridge