Skip to content

/desktop-framework-electron

Electron process architecture, IPC patterns, preload security, native APIs, packaging and distribution

shell
$ npx -y skills add agents-inc/skills --skill desktop-framework-electron --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/desktop-framework-electron
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

Electron process architecture, IPC patterns, preload security, native APIs, packaging and distribution

SKILL.md

desktop-framework-electron.SKILL.md
name: desktop-framework-electron
description: Electron process architecture, IPC patterns, preload security, native APIs, packaging and distribution

Electron Desktop Applications

> **Quick Guide:** Electron apps run two process types: a **main process** (Node.js, manages windows and system APIs) and **renderer processes** (Chromium, one per window). All communication between them flows through IPC via a preload script that uses `contextBridge` to expose a minimal, typed API surface. Never disable `contextIsolation`. Never enable `nodeIntegration` in renderers. Package with Electron Forge or Electron Builder. Auto-update via `autoUpdater` (Squirrel on macOS/Windows) or `electron-updater` for all platforms.

---

<critical_requirements>

CRITICAL: Before Using This Skill

> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)

**(You MUST keep `contextIsolation: true` (the default) -- disabling it exposes the entire preload scope to untrusted renderer code)**

**(You MUST use `contextBridge.exposeInMainWorld()` in preload scripts -- never expose `ipcRenderer` directly)**

**(You MUST NOT enable `nodeIntegration: true` in any BrowserWindow -- it gives renderers full Node.js access, which is a critical security vulnerability)**

**(You MUST validate and sanitize ALL data received via IPC in the main process -- treat renderer messages as untrusted input)**

**(You MUST use `ipcMain.handle()` / `ipcRenderer.invoke()` for request-response IPC -- avoid `sendSync` which blocks the renderer)**

**(You MUST NOT load remote URLs with `nodeIntegration` or disabled `contextIsolation` -- this is equivalent to giving the remote site full system access)**

</critical_requirements>

---

**Auto-detection:** Electron, electron, BrowserWindow, ipcMain, ipcRenderer, contextBridge, preload, webPreferences, electron-builder, electron-forge, app.whenReady, electronAPI, mainWindow, autoUpdater, nativeTheme, safeStorage, Tray, Menu, dialog, protocol, shell

**When to use:**

  • Building cross-platform desktop applications
  • Configuring main process / renderer process architecture
  • Setting up secure IPC communication patterns
  • Integrating with native OS features (tray, menus, dialogs, notifications, file system)
  • Packaging and distributing desktop applications
  • Implementing auto-update functionality
  • Registering custom protocol handlers / deep links

**When NOT to use:**

  • Choosing a UI framework for the renderer (use the appropriate web framework skill)
  • Styling the renderer UI (use the appropriate styling skill)
  • Server-side or backend logic not related to the main process
  • Mobile applications (Electron is desktop-only)
  • CLI tools that do not need a GUI

---

<patterns>

Key Patterns

Pattern 1: Secure BrowserWindow Creation

Every BrowserWindow must use a preload script and rely on the secure defaults: `contextIsolation: true`, `sandbox: true`, `nodeIntegration: false`.

const mainWindow = new BrowserWindow({
  width: 1200,
  height: 800,
  webPreferences: {
    preload: path.join(__dirname, "preload.js"),
    // contextIsolation: true  -- default since Electron 12
    // sandbox: true           -- default since Electron 20
    // nodeIntegration: false  -- default since Electron 5
  },
});

**Key point:** Never override the security defaults. The preload script is the ONLY bridge between main and renderer. See [examples/core.md](examples/core.md).

---

Pattern 2: Preload with contextBridge

The preload script exposes a narrow, explicitly typed API to the renderer. Never expose `ipcRenderer` directly.

// preload.js
const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("electronAPI", {
  readFile: (filePath) => ipcRenderer.invoke("read-file", filePath),
  onUpdateAvailable: (callback) => {
    ipcRenderer.on("update-available", (_event, data) => callback(data));
  },
});

**Key point:** Each exposed method wraps a single IPC channel. The renderer calls `window.electronAPI.readFile(path)` with no knowledge of IPC internals. See [examples/core.md](examples/core.md).

---

Pattern 3: IPC Request-Response (invoke/handle)

Use `ipcMain.handle()` in main and `ipcRenderer.invoke()` in preload for async two-way communication.

// main process
ipcMain.handle("read-file", async (_event, filePath) => {
  const content = await fs.readFile(filePath, "utf-8");
  return { success: true, content };
});

**Key point:** `handle`/`invoke` returns a Promise. Always validate `filePath` in the handler -- never trust renderer input. See [examples/ipc.md](examples/ipc.md) for all IPC patterns.

---

Pattern 4: Main-to-Renderer Messages

Use `webContents.send()` from main and listen in the preload with a callback pattern.

// main: send to specific window
mainWindow.webContents.send("update-progress", { percent: 45 });

// preload: expose listener
onUpdateProgress: (callback) => {
  ipcRenderer.on("update-progress", (_event, data) => callback(data));
},

**Key point:** The renderer cannot pull from main -- main must push. Always scope listeners to specific channels. See [examples/ipc.md](examples/ipc.md).

---

Pattern 5: App Lifecycle

The main process manages the app lifecycle with platform-specific conventions.

app.whenReady().then(() => {
  createWindow();
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on("window-all-closed", () => {
  if (process.platform !== "darwin") app.quit();
});

**Key point:** macOS apps stay alive when all windows close (`window-all-closed` should not quit). The `activate` event recreates a window when the dock icon is clicked. See [examples/core.md](examples/core.md).

---

Pattern 6: Native OS Integration

Electron exposes native APIs for dialogs, menus, tray icons, notifications,

Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withagents-inc-skills

The official skills marketplace for Agents Inc. 150+ skills covering everything from React and Prisma to Redis, ElevenLabs, and infrastructure tooling. Pick the skills that match your stack and install them via Claude Code. Need more control?

Get the whole plugin, auto-invoked