# Electron Ipc

> Safe Electron IPC patterns for renderer-main communication with preload bridges, channel design, replies, and serialization limits.

- Skill: `tympanix/electron-ipc` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tympanix/electron-ipc`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tympanix/electron-ipc/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tympanix (https://skillmd.com/u/tympanix)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tympanix/electron-ipc

---


# Skill: Electron IPC

## Purpose

Use this skill when you need to design or implement communication between
Electron's main and renderer processes.

## Core Model

- IPC is how Electron processes exchange messages.
- Define your own channel names with `ipcMain` and `ipcRenderer`.
- Channels are arbitrary and bidirectional, but keep names explicit and scoped.
- In context-isolated apps, expose narrow preload APIs with `contextBridge`
  instead of exposing raw Electron modules to the renderer.

## Default Rules

1. Keep privileged work in the main process.
2. Expose small, task-specific preload APIs such as `openFile()` or
   `setTitle(title)`.
3. Do not expose `ipcRenderer.send`, `ipcRenderer.invoke`, or `ipcRenderer.on`
   directly to renderer code.
4. When forwarding events from preload, strip the Electron event object and pass
   only the data the renderer needs.
5. Prefer namespaced channel names such as `dialog:openFile` for readability.

## Pick the Right Pattern

| Need | Renderer API | Main API | Notes |
|---|---|---|---|
| Fire-and-forget renderer -> main | `ipcRenderer.send` | `ipcMain.on` | Good for commands with no result |
| Request/response renderer -> main | `ipcRenderer.invoke` | `ipcMain.handle` | Preferred two-way pattern |
| Push main -> renderer | preload listener wrapper | `webContents.send` | Main must target a specific window/webContents |
| Renderer -> renderer | relay through main or `MessagePort` | main as broker if needed | No direct IPC channel between renderers |

## Pattern 1: Renderer to Main, One Way

Use this when the renderer triggers a main-process action and does not need a
result.

### Main

```js
ipcMain.on('set-title', (event, title) => {
  const win = BrowserWindow.fromWebContents(event.sender)
  win?.setTitle(title)
})
```

### Preload

```js
contextBridge.exposeInMainWorld('electronAPI', {
  setTitle: (title) => ipcRenderer.send('set-title', title)
})
```

### Renderer

```js
window.electronAPI.setTitle('New title')
```

## Pattern 2: Renderer to Main, Two Way

Use this when the renderer needs a result from the main process. Prefer this
over older reply patterns.

### Main

```js
ipcMain.handle('dialog:openFile', async () => {
  const { canceled, filePaths } = await dialog.showOpenDialog()
  return canceled ? undefined : filePaths[0]
})
```

### Preload

```js
contextBridge.exposeInMainWorld('electronAPI', {
  openFile: () => ipcRenderer.invoke('dialog:openFile')
})
```

### Renderer

```js
const filePath = await window.electronAPI.openFile()
```

### Notes

- `invoke`/`handle` is the preferred async request-response model.
- Errors from `ipcMain.handle` are serialized; the renderer does not receive the
  full original error object.

## Pattern 3: Main to Renderer

Use this when the main process needs to push updates into a specific window.

### Main

```js
mainWindow.webContents.send('update-counter', 1)
```

### Preload

```js
contextBridge.exposeInMainWorld('electronAPI', {
  onUpdateCounter: (callback) => {
    ipcRenderer.on('update-counter', (_event, value) => callback(value))
  }
})
```

### Renderer

```js
window.electronAPI.onUpdateCounter((value) => {
  // update UI
})
```

### Replying Back

There is no `invoke` equivalent from main to renderer. If the renderer needs to
reply, send a new message back to the main process on a separate channel.

## Legacy Patterns

Avoid these unless you have a strong compatibility reason:

- `ipcRenderer.send` plus `event.reply` for two-way communication:
  works, but forces manual reply-channel bookkeeping.
- `ipcRenderer.sendSync`: blocks the renderer and hurts responsiveness.

## Security Checklist

- Never hand the renderer unrestricted Electron or Node access.
- Do not expose raw `ipcRenderer` methods over `contextBridge`.
- Wrap listeners so the callback never receives the Electron event object.
- Keep preload APIs minimal and purpose-built.
- Validate inputs in the main process before using privileged APIs.

## Serialization Limits

Electron IPC uses the Structured Clone Algorithm.

- Safe: plain objects, arrays, strings, numbers, booleans, and other structured
  clone compatible values.
- Not safe: DOM objects, Electron objects like `BrowserWindow` or
  `WebContents`, and Node/Electron objects backed by native C++ classes.

Pass serializable data, not live framework objects.

## Quick Guidance

- Need a command without a return value: `send` + `on`
- Need a result: `invoke` + `handle`
- Need to notify a renderer from main: `webContents.send`
- Need renderer-to-renderer: broker via main or use `MessagePort`

## Reference

- https://www.electronjs.org/docs/latest/tutorial/ipc

