BuildBot

Web to Desktop

Main and renderer

Lesson 1 of 5

What you'll learn

  • Understand the main process vs renderer process split
  • Create a window with the app and BrowserWindow lifecycle
  • See why renderers are sandboxed browser pages while main is Node

An Electron app is not "a website with a frame around it." It is two kinds of OS process with very different powers. There is exactly one main process — a Node.js program with full OS access that starts first, owns the application lifecycle, and creates windows. Each window then hosts a renderer process — a Chromium page that runs your HTML, CSS, and frontend JavaScript, and nothing more.

// main.js — the main process. Plain Node.js (ESM works fine too).
const { app, BrowserWindow } = require("electron");

function createWindow() {
  const win = new BrowserWindow({ width: 900, height: 600 });
  win.loadFile("index.html"); // this page becomes a renderer process
}

app.whenReady().then(() => {
  createWindow();
  // macOS: clicking the Dock icon with no windows open makes a new one
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

// Windows/Linux apps quit with their last window; macOS apps stay running
app.on("window-all-closed", () => {
  if (process.platform !== "darwin") app.quit();
});

That's the whole lifecycle skeleton: wait for app.whenReady(), construct a BrowserWindow, load a page into it. Close every window and window-all-closed fires; on macOS the app lingers (matching platform convention), elsewhere it quits.

Who owns what

The main process owns everything the OS gives a native app: the filesystem, spawning processes, windows, menus, the tray, dialogs, app quit. The renderer owns the DOM and your UI framework — and by default it is a sandboxed browser page: sandbox: true and contextIsolation: true are the modern defaults, nodeIntegration is off, and there is no require, no fs, no Node globals in your page code.

Why so strict? A renderer displays web content, and web content can be compromised — one XSS in a page that had Node access would be remote code execution on your user's machine. So Electron keeps the renderer as locked down as a browser tab, and anything privileged happens by asking the main process over IPC (next lesson's topic).

One main, many renderers

Every BrowserWindow (and every <iframe> from another origin) gets its own renderer process, but there is only ever one main process. Think of main as the server and each window as a browser tab that happens to ship inside your app.

The challenge is a JS model of the split: two EventEmitter-backed "processes" where the renderer has no OS powers and must route every capability request through main.

Two processes, one owner (JS model)

Run it. The renderer can't touch the filesystem — it emits requests, and main answers only for capabilities it actually owns.

Loading editor…
Knowledge check

Which process owns OS-level capabilities like the filesystem and app lifecycle?

Next: preload scripts and contextBridge — the narrow, safe doorway between those two worlds.

Saved on this device. Sign in to sync your progress everywhere.