javascriptroom blog

How to Run Two Independent Browsers in One Electron Window: Separate Cookies & LocalStorage for Multiple Gmail Accounts

Managing multiple online accounts—whether for work, personal use, or testing—often means juggling multiple browser windows, incognito modes, or profile switches. This can be clunky, especially when you need to access two accounts side-by-side (e.g., work and personal Gmail).

Enter Electron: a framework that lets you build cross-platform desktop apps using web technologies (HTML, CSS, JavaScript). With Electron, you can embed multiple independent "browser instances" in a single window, each with its own isolated cookies, localStorage, and session data. This means you can log into two Gmail accounts simultaneously without them interfering with each other—no more switching profiles or incognito windows!

In this guide, we’ll walk through creating a custom Electron app that hosts two independent browsers with fully separated storage. By the end, you’ll have a desktop tool to manage multiple accounts seamlessly.

2026-02

Table of Contents#

  1. Understanding Electron Basics
  2. Setting Up the Project
  3. Creating Independent Browser Instances
  4. Managing Separate Storage (Cookies & LocalStorage)
  5. Implementing the UI
  6. Testing the Application
  7. Conclusion
  8. References

1. Understanding Electron Basics#

Before diving in, let’s cover key Electron concepts:

  • Main Process vs. Renderer Process: Electron apps have two types of processes. The main process (Node.js) controls the app lifecycle and window management. The renderer process (Chromium) runs the web UI (HTML/CSS/JS) in each window.
  • Sessions: Electron uses session objects to manage browser data like cookies, localStorage, and cache. Each session is isolated by default, meaning data from one session won’t leak into another.
  • WebViews: A <webview> tag (similar to <iframe>) embeds web content in the renderer process. Unlike iframes, webviews can use custom sessions, making them ideal for isolating browser instances.

2. Setting Up the Project#

Let’s start by setting up a basic Electron project. Ensure you have Node.js (v14+) installed.

Step 1: Initialize the Project#

Create a new folder and initialize a Node.js project:

mkdir electron-multi-browser  
cd electron-multi-browser  
npm init -y  

Step 2: Install Electron#

Install Electron as a development dependency:

npm install electron --save-dev  

Step 3: Configure package.json#

Update package.json to include a start script and set main to the entry file (we’ll name it main.js):

{  
  "name": "electron-multi-browser",  
  "version": "1.0.0",  
  "main": "main.js",  
  "scripts": {  
    "start": "electron ."  
  },  
  "devDependencies": {  
    "electron": "^28.0.0"  
  }  
}  

3. Creating Independent Browser Instances#

To run two independent browsers, we’ll use two <webview> tags, each linked to a separate Electron session. Sessions are isolated via "partitions," unique identifiers that ensure storage (cookies, localStorage) isn’t shared.

Step 1: Create the Main Window (main.js)#

The main process creates the app window and configures sessions. Create main.js with the following code:

const { app, BrowserWindow } = require('electron');  
const path = require('path');  
 
// Keep a global reference to the window object to prevent garbage collection  
let mainWindow;  
 
function createWindow() {  
  // Create the main browser window  
  mainWindow = new BrowserWindow({  
    width: 1200,  
    height: 800,  
    webPreferences: {  
      // Enable nodeIntegration and contextIsolation for webview access  
      nodeIntegration: true,  
      contextIsolation: false,  
      webviewTag: true, // Required to use <webview> tags  
    },  
  });  
 
  // Load the index.html file (our UI)  
  mainWindow.loadFile('index.html');  
 
  // Open DevTools (optional, for debugging)  
  mainWindow.webContents.openDevTools();  
 
  // Emitted when the window is closed  
  mainWindow.on('closed', () => {  
    mainWindow = null;  
  });  
}  
 
// Electron is ready to create browser windows  
app.whenReady().then(createWindow);  
 
// Quit when all windows are closed (except on macOS)  
app.on('window-all-closed', () => {  
  if (process.platform !== 'darwin') app.quit();  
});  
 
app.on('activate', () => {  
  if (mainWindow === null) createWindow();  
});  

Step 2: Create the UI with WebViews (index.html)#

The renderer process (UI) will host two <webview> tags, each using a unique session partition. Create index.html:

<!DOCTYPE html>  
<html>  
<head>  
  <meta charset="UTF-8">  
  <title>Multi-Browser Electron App</title>  
  <style>  
    body { margin: 0; padding: 10px; }  
    .browser-container {  
      display: flex;  
      gap: 10px;  
      height: 100vh;  
    }  
    .browser-pane {  
      flex: 1;  
      border: 1px solid #ccc;  
      border-radius: 8px;  
      overflow: hidden;  
    }  
    .webview {  
      width: 100%;  
      height: calc(100% - 40px); /* Leave space for header */  
    }  
    .header {  
      padding: 10px;  
      background: #f0f0f0;  
      font-weight: bold;  
    }  
  </style>  
</head>  
<body>  
  <div class="browser-container">  
    <!-- Browser 1: Session "account1" -->  
    <div class="browser-pane">  
      <div class="header">Account 1 (Gmail)</div>  
      <webview  
        class="webview"  
        id="webview1"  
        partition="persist:account1"  <!-- Unique partition for isolation -->  
        src="https://mail.google.com"  
      ></webview>  
    </div>  
 
    <!-- Browser 2: Session "account2" -->  
    <div class="browser-pane">  
      <div class="header">Account 2 (Gmail)</div>  
      <webview  
        class="webview"  
        id="webview2"  
        partition="persist:account2"  <!-- Unique partition for isolation -->  
        src="https://mail.google.com"  
      ></webview>  
    </div>  
  </div>  
</body>  
</html>  

4. Managing Separate Storage (Cookies & LocalStorage)#

The magic lies in the partition attribute of the <webview> tags. Let’s break down how this ensures isolation:

How Sessions Isolate Storage#

  • Partition Names: The partition attribute uses a string like persist:account1. The persist: prefix tells Electron to save the session data to disk (so storage persists between app restarts). Without persist:, the session is in-memory and resets when the app closes.
  • Isolation: Sessions with different partition names (e.g., account1 vs. account2) never share data. Cookies, localStorage, cache, and even service worker data are stored separately.

Verifying Isolation (Optional)#

To confirm storage is separated, add code to log cookies from each session. In index.html, add a script tag:

<script>  
  // Wait for webviews to load  
  document.getElementById('webview1').addEventListener('dom-ready', () => {  
    const webview1 = document.getElementById('webview1');  
    // Get cookies for webview1's session  
    webview1.getWebContents().session.cookies.get({}, (error, cookies) => {  
      console.log('Cookies for Account 1:', cookies);  
    });  
  });  
 
  document.getElementById('webview2').addEventListener('dom-ready', () => {  
    const webview2 = document.getElementById('webview2');  
    // Get cookies for webview2's session  
    webview2.getWebContents().session.cookies.get({}, (error, cookies) => {  
      console.log('Cookies for Account 2:', cookies);  
    });  
  });  
</script>  

When you run the app, check the DevTools console. You’ll see distinct cookie lists for each account after logging in.

5. Implementing the UI#

Our current UI splits the window into two panes, each loading Gmail. Let’s enhance it with basic controls (e.g., reload buttons) and better styling.

Update the CSS in index.html to add controls:

/* Add to existing styles */  
.controls {  
  display: flex;  
  gap: 5px;  
  padding: 10px;  
  background: #f0f0f0;  
}  
button {  
  padding: 5px 10px;  
  cursor: pointer;  
}  

Update the HTML for each browser pane to include controls:

<!-- Updated Browser 1 Pane -->  
<div class="browser-pane">  
  <div class="header">Account 1 (Gmail)</div>  
  <div class="controls">  
    <button onclick="document.getElementById('webview1').reload()">Reload</button>  
    <button onclick="document.getElementById('webview1').goBack()">Back</button>  
    <button onclick="document.getElementById('webview1').goForward()">Forward</button>  
  </div>  
  <webview class="webview" id="webview1" partition="persist:account1" src="https://mail.google.com"></webview>  
</div>  
 
<!-- Repeat for Browser 2 with webview2 -->  

6. Testing the Application#

Step 1: Run the App#

Start the app with:

npm start  

Step 2: Log Into Gmail Accounts#

  • In the left pane ("Account 1"), log into your first Gmail account.
  • In the right pane ("Account 2"), log into a second Gmail account.

Step 3: Verify Isolation#

  • Close and restart the app. Both accounts should remain logged in (thanks to persist: partitions).
  • Check that actions in one pane (e.g., reading an email, composing a draft) don’t affect the other.

Troubleshooting#

  • Sessions sharing data? Ensure partition names are unique (e.g., don’t use account1 for both webviews).
  • Storage not persisting? Forgot the persist: prefix in the partition name.
  • Webview not loading? Ensure webviewTag: true is set in main.js under webPreferences.

7. Conclusion#

You’ve built an Electron app that runs two independent browsers with isolated storage! This lets you manage multiple Gmail accounts (or any web apps) side-by-side without conflicts.

Enhancements to Explore#

  • Add more webviews (e.g., 3+ accounts) by adding additional <webview> tags with unique partitions.
  • Save custom URLs per account (e.g., load Outlook in one pane and Gmail in another).
  • Add a "Clear Storage" button to reset a session’s cookies/localStorage.
  • Use session.fromPartition() in the main process to programmatically control sessions (e.g., delete persistent data).

8. References#