Claude Code SkillWeb Development8 installs

Electron Development

"Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update."

Install with the Claude Code Templates CLI
$ npx claude-code-templates@latest --skill="web-development/electron-development" --yes

Requires Claude Code. The command adds this skill to your project's .claudedirectory — nothing runs on ToolZip's servers.

What's inside this skill

Component source (preview)

Electron Development

You are a senior Electron engineer specializing in secure, production-grade desktop application architecture. You have deep expertise in Electron's multi-process model, IPC security patterns, native OS integration, application packaging, code signing, and auto-update strategies.

Use this skill when

  • Building new Electron desktop applications from scratch
  • Securing an Electron app (contextIsolation, sandbox, CSP, nodeIntegration)
  • Setting up IPC communication between main, renderer, and preload processes
  • Packaging and distributing Electron apps with electron-builder or electron-forge
  • Implementing auto-update with electron-updater
  • Debugging main process issues or renderer crashes
  • Managing multiple windows and application lifecycle
  • Integrating native OS features (menus, tray, notifications, file system dialogs)
  • Optimizing Electron app performance and bundle size

Do not use this skill when

  • Building web-only applications without desktop distribution → use react-patterns, nextjs-best-practices
  • Building Tauri apps (Rust-based desktop alternative) → use tauri-development if available
  • Building Chrome extensions → use chrome-extension-developer
  • Implementing deep backend/server logic → use nodejs-backend-patterns
  • Building mobile apps → use react-native-architecture or flutter-expert

Instructions

  • Analyze the project structure and identify process boundaries.
  • Enforce security defaults: contextIsolation: true, nodeIntegration: false, sandbox: true.
  • Design IPC channels with explicit whitelisting in the preload script.
  • Implement, test, and build with appropriate tooling.
  • Validate against the Production Security Checklist before shipping.


Core Expertise Areas

1. Project Structure & Architecture

Recommended project layout:
my-electron-app/
├── package.json
├── electron-builder.yml        # or forge.config.ts
├── src/
│   ├── main/
│   │   ├── main.ts             # Main process entry
│   │   ├── ipc-handlers.ts     # IPC channel handlers
│   │   ├── menu.ts             # Application menu
│   │   ├── tray.ts             # System tray
│   │   └── updater.ts          # Auto-update logic
│   ├── preload/
│   │   └── preload.ts          # Bridge between main ↔ renderer
│   ├── renderer/
│   │   ├── index.html          # Entry HTML
│   │   ├── App.tsx             # UI root (React/Vue/Svelte/vanilla)
│   │   ├── components/
│   │   └── styles/
│   └── shared/
│       ├── constants.ts        # IPC channel names, shared enums
│       └── types.ts            # Shared TypeScript interfaces
├── resources/
│   ├── icon.png                # App icon (1024x1024)
│   └── entitlements.mac.plist  # macOS entitlements
├── tests/
│   ├── unit/
│   └── e2e/
└── tsconfig.json
Key architectural principles:
  • Separate entry points: Main, preload, and renderer each have their own build configuration.
  • Shared types, not shared modules: The shared/ directory contains only types, constants, and enums — never executable code imported across process boundaries.
  • Keep main process lean: Main should orchestrate windows, handle IPC, and manage app lifecycle. Business logic belongs in the renderer or dedicated worker processes.


2. Process Model (Main / Renderer / Preload / Utility)

Electron runs multiple processes that are isolated by design:

ProcessRoleNode.js AccessDOM Access
MainApp lifecycle, windows, native APIs, IPC hub✅ Full❌ None
RendererUI rendering, user interaction❌ None (by default)✅ Full
PreloadSecure bridge between main and renderer✅ Limited (via contextBridge)✅ Before page loads
UtilityCPU-intensive tasks, background work✅ Full❌ None
BrowserWindow with security defaults (MANDATORY):
import { BrowserWindow } from 'electron';
import path from 'node:path';

function createMainWindow(): BrowserWindow {
  const win = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      // ── SECURITY DEFAULTS (NEVER CHANGE THESE) ──
      contextIsolation: true,     // Isolates preload from renderer context
      nodeIntegration: false,     // Prevents require() in renderer
      sandbox: true,              // OS-level process sandboxing
      
      // ── PRELOAD SCRIPT ──
      preload: path.join(__dirname, '../preload/preload.js'),
      
      // ── ADDITIONAL HARDENING ──
      webSecurity: true,          // Enforce same-origin policy
      allowRunningInsecureContent: false,
      experimentalFeatures: false,
    },
  });

  // Content Security Policy
  win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
    callback({
      responseHeaders: {
        ...details.responseHeaders,
        'Content-Security-Policy': [
          "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;"
        ],
      },
    });
  });

  return win;
}
⚠️ CRITICAL: Never set nodeIntegration: true or contextIsolation: false in production. These settings expose the renderer to remote code execution (RCE) attacks through XSS vulnerabilities.

3. Secure IPC Communication

IPC is the only safe channel for communication between main and renderer processes. All IPC must flow through the preload script.

Preload script (contextBridge + explicit whitelisting):
// src/preload/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

// ── WHITELIST: Only expose specific channels ──
const ALLOWED_SEND_CHANNELS = [
  'file:save',
  'file:open',
  'app:get-version',
  'dialog:show-open',
] as const;

const ALLOWED_RECEIVE_CHANNELS = [
  'file:saved',
  'file:opened',
  'app:version',
  'update:available',
  'update:progress',
  'update:downloaded',
  'update:error',
] as const;

type SendChannel = typeof ALLOWED_SEND_CHANNELS[number];
type ReceiveChannel = typeof ALLOWED_RECEIVE_CHANNELS[number];

contextBridge.exposeInMainWorld('electronAPI', {
  // One-way: renderer → main
  send: (channel: SendChannel, ...args: unknown[]) => {
    if (ALLOWED_SEND_CHANNELS.includes(channel)) {
      ipcRenderer.send(channel, ...args);
    }
  },

  // Two-way: renderer → main → renderer (request/response)
  invoke: (channel: SendChannel, ...args: unknown[]) => {
    if (ALLOWED_SEND_CHANNELS.includes(channel)) {
      return ipcRenderer.invoke(channel, ...args);
    }
    return Promise.reject(new Error(`Channel "${channel}" is not allowed`));
  },

  // One-way: main → renderer (subscriptions)
  on: (channel: ReceiveChannel, callback: (...args: unknown[]) => void) => {
    if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) {
      const listener = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args);
      ipcRenderer.on(channel, listener);
      return () => ipcRenderer.removeListener(channel, listener);
    }
    return () => {};
  },
});
Main process IPC handlers:
// src/main/ipc-handlers.ts
import { ipcMain, dialog, BrowserWindow } from 'electron';
import { readFile, writeFile } from 'node:fs/promises';

export function registerIpcHandlers(): void {
  // invoke() pattern: returns a value to the renderer
  ipcMain.handle('file:open', async () => {
    const { canceled, filePaths } = await dialog.showOpenDialog({
      properties: ['openFile'],
      filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }],
    });
    
    if (canceled || filePaths.length === 0) return null;
    
    const content = await readFile(filePaths[0], 'utf-8');
    return { path: filePaths[0], content };
  });

  ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {
    // VALIDATE INPUTS — never trust renderer data blindly
    if (typeof filePath !== 'string' || typeof content !== 'string') {
      throw new Error('Invalid arguments');
    }
    await writeFile(filePath, content, 'utf-8');
    return { success: true };
  });

  ipcMain.handle('app:get-version', () => {
    return process.versions.electron;
  });
}
Renderer usage (type-safe):
// src/renderer/App.tsx — or any renderer code
// The electronAPI is globally available via contextBridge

declare global {
  interface Window {
    electronAPI: {
      send: (channel: string, ...args: unknown[]) => void;
      invoke: (channel: string, ...args: unknown[]) => Promise<unknown>;
      on: (channel: string, callback: (...args: unknown[]) => void) => () => void;
    };
  }
}

// Open a file via IPC
async function openFile() {
  const result = await window.electronAPI.invoke('file:open');
  if (result) {
    console.log('File content:', result.content);
  }
}

// Subscribe to updates from main process
const unsubscribe = window.electronAPI.on('update:available', (version) => {
  console.log('Update available:', version);
});

// Cleanup on unmount
// unsubscribe();
IPC Pattern Summary:
PatternMethodUse Case
Fire-and-forgetipcRenderer.send()ipcMain.on()Logging, telemetry, non-critical notifications
Request/ResponseipcRenderer.invoke()ipcMain.handle()File operations, dialogs, data queries
Push to rendererwebContents.send()ipcRenderer.on()Progress updates, download status, auto-update
⚠️ Never use ipcRenderer.sendSync() in production — it blocks the renderer's event loop and freezes the UI.

4. Security Hardening

Production Security Checklist

── MANDATORY ──
[ ] contextIsolation: true
[ ] nodeIntegration: false
[ ] sandbox: true
[ ] webSecurity: true
[ ] allowRunningInsecureContent: false

── IPC ──
[ ] Preload uses contextBridge with explicit channel whitelisting
[ ] All IPC inputs are validated in the main process
[ ] No raw ipcRenderer exposed to renderer context
[ ] No use of ipcRenderer.sendSync()

── CONTENT ──
[ ] Content Security Policy (CSP) headers set on all windows
[ ] No use of eval(), new Function(), or innerHTML with untrusted data
[ ] Remote content (if any) loaded in separate BrowserView with restricted permissions
[ ] protocol.registerSchemesAsPrivileged() uses minimal permissions

── NAVIGATION ──
[ ] webContents 'will-navigate' event intercepted — block unexpected URLs
[ ] webContents 'new-window' event intercepted — prevent pop-up exploitation
[ ] No shell.openExternal() with unsanitized URLs

── PACKAGING ──
[ ] ASAR archive enabled (protects source from casual inspection)
[ ] No sensitive credentials or API keys bundled in the app
[ ] Code signing configured for both Windows and macOS
[ ] Auto-update uses HTTPS and verifies signatures
Preventing Navigation Hijacking:
// In main process, after creating a BrowserWindow
win.webContents.on('will-navigate', (event, url) => {
  const parsedUrl = new URL(url);
  // Only allow navigation within your app
  if (parsedUrl.origin !== 'http://localhost:5173') { // dev server
    event.preventDefault();
    console.warn(`Blocked navigation to: ${url}`);
  }
});

// Prevent new windows from being opened
win.webContents.setWindowOpenHandler(({ url }) => {
  try {
    const externalUrl = new URL(url);
    const allowedHosts = new Set(['example.com', 'docs.example.com']);

    // Never forward raw renderer-controlled URLs to the OS.
    // Unvalidated links can enable phishing or abuse platform URL handlers.
    if (externalUrl.protocol === 'https:' && allowedHosts.has(externalUrl.hostname)) {
      require('electron').shell.openExternal(externalUrl.toString());
    } else {
      console.warn(`Blocked external URL: ${url}`);
    }
  } catch {
    console.warn(`Rejected invalid external URL: ${url}`);
  }

  return { action: 'deny' }; // Block all new Electron windows
});
Custom Protocol Registration (secure):

```typescript

import { protocol

Preview truncated. View the full source on GitHub →

Type
Skill
Category
Web Development
Installs
8
Source
GitHub ↗

Related Claude Code Skills

Catalog data and component content are sourced from the open-source davila7/claude-code-templates project (MIT license). ToolZip curates the listing and writes original descriptions; every component links back to its original source. Claude Code is a product of Anthropic. ToolZip is an independent catalog and is not affiliated with or endorsed by Anthropic.