| name | electron-ipc |
| description | Safe Electron IPC patterns for renderer-main communication with preload bridges, channel design, replies, and serialization limits.
|
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
- Keep privileged work in the main process.
- Expose small, task-specific preload APIs such as
openFile() or
setTitle(title).
- Do not expose
ipcRenderer.send, ipcRenderer.invoke, or ipcRenderer.on
directly to renderer code.
- When forwarding events from preload, strip the Electron event object and pass
only the data the renderer needs.
- 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
ipcMain.on('set-title', (event, title) => {
const win = BrowserWindow.fromWebContents(event.sender)
win?.setTitle(title)
})
Preload
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
Renderer
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
ipcMain.handle('dialog:openFile', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog()
return canceled ? undefined : filePaths[0]
})
Preload
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
})
Renderer
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
mainWindow.webContents.send('update-counter', 1)
Preload
contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCounter: (callback) => {
ipcRenderer.on('update-counter', (_event, value) => callback(value))
}
})
Renderer
window.electronAPI.onUpdateCounter((value) => {
})
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