Skip to content

Inter Process Communication(IPC)

Electron - Inter-Process Communication (IPC)

Section titled “Electron - Inter-Process Communication (IPC)”

Electron applications have at least two types of processes: one main process and one or more renderer processes. Inter-Process Communication (IPC) is essential for these processes to exchange information.

Electron provides modules for IPC: ipcMain (for the main process) and ipcRenderer (for renderer processes). These allow asynchronous and, in some cases, synchronous communication.

The ipcMain module, used in the main process, handles messages sent from renderer processes. It can reply to these messages or initiate communication with renderers.

The ipcRenderer module, used in preload scripts or renderer processes (when Node integration is enabled, though not recommended for security), sends messages to the main process and handles replies.

Modern Electron development emphasizes security. This means nodeIntegration is typically disabled, and contextIsolation is enabled in renderer processes. Communication with the main process is facilitated securely through a preload script using contextBridge.

Let’s create an example demonstrating asynchronous, synchronous (with a caution), and invoke/handle (request/response) IPC patterns.

First, create your main process file, main.js:

const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true, // Protect against prototype pollution
nodeIntegration: false, // Keep Node.js integration disabled in renderer
}
});
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// mainWindow.webContents.openDevTools(); // Optional: Open DevTools
}
// Asynchronous message handler from renderer
ipMain.on('asynchronous-message', (event, arg) => {
console.log('Main process received async:', arg); // Logs 'async ping'
// Reply to the sender (renderer process)
event.reply('asynchronous-reply', 'async pong from main');
});
// Synchronous message handler from renderer
// Note: Avoid synchronous IPC for performance reasons unless absolutely necessary.
ipcMain.on('synchronous-message', (event, arg) => {
console.log('Main process received sync:', arg); // Logs 'sync ping'
event.returnValue = 'sync pong from main';
});
// Handle for invoke/handle pattern (request/response)
ipMain.handle('invoke-message', async (event, arg) => {
console.log('Main process received invoke:', arg); // Logs 'invoke ping'
// Perform some async operation if needed
// const result = await somePromise();
return 'pong from handle in main';
});
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

Next, create a preload.js file in the same directory. This script runs before the web page in the renderer process and has access to Node.js APIs. It securely exposes IPC functionality to the renderer:

const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
send: (channel, data) => ipcRenderer.send(channel, data),
sendSync: (channel, data) => ipcRenderer.sendSync(channel, data),
on: (channel, func) => {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
},
invoke: (channel, data) => ipcRenderer.invoke(channel, data),
// It's good practice to be specific about which channels are exposed
// e.g., openFile: () => ipcRenderer.invoke('dialog:openFile')
});

Now, create your index.html file:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron IPC Demo</title>
</head>
<body>
<h1>Electron IPC Communication</h1>
<p>Check the console in both this window (Renderer) and your terminal (Main).</p>
<script>
// 1. Asynchronous IPC: send and listen for reply
console.log('Renderer: Sending async message...');
window.electronAPI.send('asynchronous-message', 'async ping from renderer');
window.electronAPI.on('asynchronous-reply', (reply) => {
console.log('Renderer received async reply:', reply); // 'async pong from main'
});
// 2. Synchronous IPC: send and get immediate result
// WARNING: Synchronous IPC can block the renderer process. Use sparingly.
console.log('Renderer: Sending sync message...');
const syncReply = window.electronAPI.sendSync('synchronous-message', 'sync ping from renderer');
console.log('Renderer received sync reply:', syncReply); // 'sync pong from main'
// 3. Invoke/Handle IPC: request/response pattern (Promise-based)
async function testInvoke() {
console.log('Renderer: Sending invoke message...');
const invokeReply = await window.electronAPI.invoke('invoke-message', 'invoke ping from renderer');
console.log('Renderer received invoke reply:', invokeReply); // 'pong from handle in main'
}
testInvoke();
</script>
</body>
</html>

To run the app, ensure your package.json has "main": "main.js" and a start script like "start": "electron .". Then run:

npm start

Output in your application’s DevTools console (Renderer):

Renderer: Sending async message...
Renderer: Sending sync message...
Renderer received sync reply: sync pong from main
Renderer: Sending invoke message...
Renderer received async reply: async pong from main
Renderer received invoke reply: pong from handle in main

Output in your terminal console (Main):

Main process received async: async ping from renderer
Main process received sync: sync ping from renderer
Main process received invoke: invoke ping from renderer

Key Takeaways:

  • Always prioritize asynchronous IPC (send/on or invoke/handle) to avoid blocking processes and keep your UI responsive.
  • Use preload scripts with contextBridge to securely expose IPC channels and Node.js functionality to renderer processes when contextIsolation is enabled (which is the default and recommended).
  • The invoke/handle pattern is excellent for request-response type interactions, returning a Promise.
  • Avoid using the remote module as it’s deprecated and has security implications.

For further details, consult the official Electron documentation on Inter-Process Communication and Preload Scripts.