--- title: Inter-Process Communication description: Use the ipcMain and ipcRenderer modules to communicate between Electron processes slug: ipc hide_title: false --- # Inter-Process Communication Inter-process communication (IPC) is a key part of building feature-rich desktop applications in Electron. Because the main and renderer processes have different responsibilities in Electron's process model, IPC is the only way to perform many common tasks, such as calling a native API from your UI or triggering changes in your web contents from native menus. ## IPC channels In Electron, processes communicate by passing messages through developer-defined "channels" with the [`ipcMain`] and [`ipcRenderer`] modules. These channels are **arbitrary** (you can name them anything you want) and **bidirectional** (you can use the same channel name for both modules). In this guide, we'll be going over some fundamental IPC patterns with concrete examples that you can use as a reference for your app code. ## Understanding context-isolated processes Before proceeding to implementation details, you should be familiar with the idea of using a [preload script] to import Node.js and Electron modules in a context-isolated renderer process. * For a full overview of Electron's process model, you can read the [process model docs]. * For a primer into exposing APIs from your preload script using the `contextBridge` module, check out the [context isolation tutorial]. ## Pattern 1: Renderer to main (one-way) To fire a one-way IPC message from a renderer process to the main process, you can use the [`ipcRenderer.send`] API to send a message that is then received by the [`ipcMain.on`] API. You usually use this pattern to call a main process API from your web contents. We'll demonstrate this pattern by creating a simple app that can programmatically change its window title. For this demo, you'll need to add code to your main process, your renderer process, and a preload script. The full code is below, but we'll be explaining each file individually in the following sections. ```fiddle docs/fiddles/ipc/pattern-1 ``` ### 1. Listen for events with `ipcMain.on` In the main process, set an IPC listener on the `set-title` channel with the `ipcMain.on` API: ```javascript {6-10,22} title='main.js (Main Process)' const {app, BrowserWindow, ipcMain} = require('electron') const path = require('path') //... function handleSetTitle (event, title) { const webContents = event.sender const win = BrowserWindow.fromWebContents(webContents) win.setTitle(title) } function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadFile('index.html') } app.whenReady().then(() => { ipcMain.on('set-title', handleSetTitle) createWindow() } //... ``` The above `handleSetTitle` callback has two parameters: an [IpcMainEvent] structure and a `title` string. Whenever a message comes through the `set-title` channel, this function will find the BrowserWindow instance attached to the message sender and use the `win.setTitle` API on it. :::info Make sure you're loading the `index.html` and `preload.js` entry points for the following steps! ::: ### 2. Expose `ipcRenderer.send` via preload To send messages to the listener created above, you can use the `ipcRenderer.send` API. By default, the renderer process has no Node.js or Electron module access. As an app developer, you need to choose which APIs to expose from your preload script using the `contextBridge` API. In your preload script, add the following code, which will expose a global `window.electronAPI` variable to your renderer process. ```javascript title='preload.js (Preload Script)' const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { setTitle: (title) => ipcRenderer.send('set-title', title) }) ``` At this point, you'll be able to use the `window.electronAPI.setTitle()` function in the renderer process. :::caution Security warning We don't directly expose the whole `ipcRenderer.send` API for [security reasons]. Make sure to limit the renderer's access to Electron APIs as much as possible. ::: ### 3. Build the renderer process UI In our BrowserWindow's loaded HTML file, add a basic user interface consisting of a text input and a button: ```html {11-12} title='index.html' Hello World! Title: ``` To make these elements interactive, we'll be adding a few lines of code in the imported `renderer.js` file that leverages the `window.electronAPI` functionality exposed from the preload script: ```javascript title='renderer.js (Renderer Process)' const setButton = document.getElementById('btn') const titleInput = document.getElementById('title') setButton.addEventListener('click', () => { const title = titleInput.value window.electronAPI.setTitle(title) }); ``` At this point, your demo should be fully functional. Try using the input field and see what happens to your BrowserWindow title! ## Pattern 2: Renderer to main (two-way) A common application for two-way IPC is calling a main process module from your renderer process code and waiting for a result. This can be done by using [`ipcRenderer.invoke`] paired with [`ipcMain.handle`]. In the following example, we'll be opening a native file dialog from the renderer process and returning the selected file's path. For this demo, you'll need to add code to your main process, your renderer process, and a preload script. The full code is below, but we'll be explaining each file individually in the following sections. ```fiddle docs/fiddles/ipc/pattern-2 ``` ### 1. Listen for events with `ipcMain.handle` In the main process, we'll be creating a `handleFileOpen()` function that calls `dialog.showOpenDialog` and returns the value of the file path selected by the user. This function is used as a callback whenever an `ipcRender.invoke` message is sent through the `dialog:openFile` channel from the renderer process. The return value is then returned as a Promise to the original `invoke` call. :::caution A word on error handling Errors thrown through `handle` in the main process are not transparent as they are serialized and only the `message` property from the original error is provided to the renderer process. Please refer to [#24427](https://github.com/electron/electron/issues/24427) for details. ::: ```javascript {6-13,25} title='main.js (Main Process)' const { BrowserWindow, dialog, ipcMain } = require('electron') const path = require('path') //... async function handleFileOpen() { const { canceled, filePaths } = await dialog.showOpenDialog() if (canceled) { return } else { return filePaths[0] } } function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) mainWindow.loadFile('index.html') } app.whenReady(() => { ipcMain.handle('dialog:openFile', handleFileOpen) createWindow() }) //... ``` :::tip on channel names The `dialog:` prefix on the IPC channel name has no effect on the code. It only serves as a namespace that helps with code readability. ::: :::info Make sure you're loading the `index.html` and `preload.js` entry points for the following steps! ::: ### 2. Expose `ipcRenderer.invoke` via preload In the preload script, we expose a one-line `openFile` function that calls and returns the value of `ipcRenderer.invoke('dialog:openFile')`. We'll be using this API in the next step to call the native dialog from our renderer's user interface. ```javascript title='preload.js (Preload Script)' const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { openFile: () => ipcRenderer.invoke('dialog:openFile') }) ``` :::caution Security warning We don't directly expose the whole `ipcRenderer.invoke` API for [security reasons]. Make sure to limit the renderer's access to Electron APIs as much as possible. ::: ### 3. Build the renderer process UI Finally, let's build the HTML file that we load into our BrowserWindow. ```html {10-11} title='index.html' Dialog File path: ``` The UI consists of a single `#btn` button element that will be used to trigger our preload API, and a `#filePath` element that will be used to display the path of the selected file. Making these pieces work will take a few lines of code in the renderer process script: ```javascript title='renderer.js (Renderer Process)' const btn = document.getElementById('btn') const filePathElement = document.getElementById('filePath') btn.addEventListener('click', async () => { const filePath = await window.electronAPI.openFile() filePathElement.innerText = filePath }) ``` In the above snippet, we listen for clicks on the `#btn` button, and call our `window.electronAPI.openFile()` API to activate the native Open File dialog. We then display the selected file path in the `#filePath` element. ### Note: legacy approaches The `ipcRenderer.invoke` API was added in Electron 7 as a developer-friendly way to tackle two-way IPC from the renderer process. However, there exist a couple alternative approaches to this IPC pattern. :::warning Avoid legacy approaches if possible We recommend using `ipcRenderer.invoke` whenever possible. The following two-way renderer-to-main patterns are documented for historical purposes. ::: :::info For the following examples, we're calling `ipcRenderer` directly from the preload script to keep the code samples small. ::: #### Using `ipcRenderer.send` The `ipcRenderer.send` API that we used for single-way communication can also be leveraged to perform two-way communication. This was the recommended way for asynchronous two-way communication via IPC prior to Electron 7. ```javascript title='preload.js (Preload Script)' // You can also put expose this code to the renderer // process with the `contextBridge` API const { ipcRenderer } = require('electron') ipcRenderer.on('asynchronous-reply', (_event, arg) => { console.log(arg) // prints "pong" in the DevTools console }) ipcRenderer.send('asynchronous-message', 'ping') ``` ```javascript title='main.js (Main Process)' ipcMain.on('asynchronous-message', (event, arg) => { console.log(arg) // prints "ping" in the Node console // works like `send`, but returning a message back // to the renderer that sent the original message event.reply('asynchronous-reply', 'pong') }) ``` There are a couple downsides to this approach: * You need to set up a second `ipcRenderer.on` listener to handle the response in the renderer process. With `invoke`, you get the response value returned as a Promise to the original API call. * There's no obvious way to pair the `asynchronous-reply` message to the original `asynchronous-message` one. If you have very frequent messages going back and forth through these channels, you would need to add additional app code to track each call and response individually. #### Using `ipcRenderer.sendSync` The `ipcRenderer.sendSync` API sends a message to the main process and waits _synchronously_ for a response. ```javascript title='main.js (Main Process)' const { ipcMain } = require('electron') ipcMain.on('synchronous-message', (event, arg) => { console.log(arg) // prints "ping" in the Node console event.returnValue = 'pong' }) ``` ```javascript title='preload.js (Preload Script)' // You can also put expose this code to the renderer // process with the `contextBridge` API const { ipcRenderer } = require('electron') const result = ipcRenderer.sendSync('synchronous-message', 'ping') console.log(result) // prints "pong" in the DevTools console ``` The structure of this code is very similar to the `invoke` model, but we recommend **avoiding this API** for performance reasons. Its synchronous nature means that it'll block the renderer process until a reply is received. ## Pattern 3: Main to renderer When sending a message from the main process to a renderer process, you need to specify which renderer is receiving the message. Messages need to be sent to a renderer process via its [`WebContents`] instance. This WebContents instance contains a [`send`][webcontents-send] method that can be used in the same way as `ipcRenderer.send`. To demonstrate this pattern, we'll be building a number counter controlled by the native operating system menu. For this demo, you'll need to add code to your main process, your renderer process, and a preload script. The full code is below, but we'll be explaining each file individually in the following sections. ```fiddle docs/fiddles/ipc/pattern-3 ``` ### 1. Send messages with the `webContents` module For this demo, we'll need to first build a custom menu in the main process using Electron's `Menu` module that uses the `webContents.send` API to send an IPC message from the main process to the target renderer. ```javascript {11-26} title='main.js (Main Process)' const {app, BrowserWindow, Menu, ipcMain} = require('electron') const path = require('path') function createWindow () { const mainWindow = new BrowserWindow({ webPreferences: { preload: path.join(__dirname, 'preload.js') } }) const menu = Menu.buildFromTemplate([ { label: app.name, submenu: [ { click: () => mainWindow.webContents.send('update-counter', 1), label: 'Increment', }, { click: () => mainWindow.webContents.send('update-counter', -1), label: 'Decrement', } ] } ]) Menu.setApplicationMenu(menu) mainWindow.loadFile('index.html') } //... ``` For the purposes of the tutorial, it's important to note that the `click` handler sends a message (either `1` or `-1`) to the renderer process through the `update-counter` channel. ```javascript click: () => mainWindow.webContents.send('update-counter', -1) ``` :::info Make sure you're loading the `index.html` and `preload.js` entry points for the following steps! ::: ### 2. Expose `ipcRenderer.on` via preload Like in the previous renderer-to-main example, we use the `contextBridge` and `ipcRenderer` modules in the preload script to expose IPC functionality to the renderer process: ```javascript title='preload.js (Preload Script)' const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { onUpdateCounter: (callback) => ipcRenderer.on('update-counter', callback) }) ``` After loading the preload script, your renderer process should have access to the `window.electronAPI.onUpdateCounter()` listener function. :::caution Security warning We don't directly expose the whole `ipcRenderer.on` API for [security reasons]. Make sure to limit the renderer's access to Electron APIs as much as possible. ::: :::info In the case of this minimal example, you can call `ipcRenderer.on` directly in the preload script rather than exposing it over the context bridge. ```javascript title='preload.js (Preload Script)' const { ipcRenderer } = require('electron') window.addEventListener('DOMContentLoaded', () => { const counter = document.getElementById('counter') ipcRenderer.on('update-counter', (_event, value) => { const oldValue = Number(counter.innerText) const newValue = oldValue + value counter.innerText = newValue }) }) ``` However, this approach has limited flexibility compared to exposing your preload APIs over the context bridge, since your listener can't directly interact with your renderer code. ::: ### 3. Build the renderer process UI To tie it all together, we'll create an interface in the loaded HTML file that contains a `#counter` element that we'll use to display the values: ```html {10} title='index.html' Menu Counter Current value: 0 ``` Finally, to make the values update in the HTML document, we'll add a few lines of DOM manipulation so that the value of the `#counter` element is updated whenever we fire an `update-counter` event. ```javascript title='renderer.js (Renderer Process)' const counter = document.getElementById('counter') window.electronAPI.onUpdateCounter((_event, value) => { const oldValue = Number(counter.innerText) const newValue = oldValue + value counter.innerText = newValue }) ``` In the above code, we're passing in a callback to the `window.electronAPI.onUpdateCounter` function exposed from our preload script. The second `value` parameter corresponds to the `1` or `-1` we were passing in from the `webContents.send` call from the native menu. ### Optional: returning a reply There's no equivalent for `ipcRenderer.invoke` for main-to-renderer IPC. Instead, you can send a reply back to the main process from within the `ipcRenderer.on` callback. We can demonstrate this with slight modifications to the code from the previous example. In the renderer process, use the `event` parameter to send a reply back to the main process through the `counter-value` channel. ```javascript title='renderer.js (Renderer Process)' const counter = document.getElementById('counter') window.electronAPI.onUpdateCounter((event, value) => { const oldValue = Number(counter.innerText) const newValue = oldValue + value counter.innerText = newValue event.sender.send('counter-value', newValue) }) ``` In the main process, listen for `counter-value` events and handle them appropriately. ```javascript title='main.js (Main Process)' //... ipcMain.on('counter-value', (_event, value) => { console.log(value) // will print value to Node console }) //... ``` ## Pattern 4: Renderer to renderer There's no direct way to send messages between renderer processes in Electron using the `ipcMain` and `ipcRenderer` modules. To achieve this, you have two options: * Use the main process as a message broker between renderers. This would involve sending a message from one renderer to the main process, which would forward the message to the other renderer. * Pass a [MessagePort] from the main process to both renderers. This will allow direct communication between renderers after the initial setup. ## Object serialization Electron's IPC implementation uses the HTML standard [Structured Clone Algorithm][sca] to serialize objects passed between processes, meaning that only certain types of objects can be passed through IPC channels. In particular, DOM objects (e.g. `Element`, `Location` and `DOMMatrix`), Node.js objects backed by C++ classes (e.g. `process.env`, some members of `Stream`), and Electron objects backed by C++ classes (e.g. `WebContents`, `BrowserWindow` and `WebFrame`) are not serializable with Structured Clone. [context isolation tutorial]: context-isolation.md [security reasons]: ./context-isolation.md#security-considerations [`ipcMain`]: ../api/ipc-main.md [`ipcMain.handle`]: ../api/ipc-main.md#ipcmainhandlechannel-listener [`ipcMain.on`]: ../api/ipc-main.md [IpcMainEvent]: ../api/structures/ipc-main-event.md [`ipcRenderer`]: ../api/ipc-renderer.md [`ipcRenderer.invoke`]: ../api/ipc-renderer.md#ipcrendererinvokechannel-args [`ipcRenderer.send`]: ../api/ipc-renderer.md [MessagePort]: ./message-ports.md [preload script]: process-model.md#preload-scripts [process model docs]: process-model.md [sca]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm [`WebContents`]: ../api/web-contents.md [webcontents-send]: ../api/web-contents.md#contentssendchannel-args