Skip to content
electron-desktop logo

Electron Desktop Development

electron-desktop

Desktop application development with Electron for Windows, macOS, and Linux. Use when building cross-platform desktop apps, implementing native OS features, or packaging web apps for desktop.

SKILL.md

Full skill instructions

Electron Desktop Development

Build cross-platform desktop applications using web technologies.

Platforms Supported

PlatformArchitectureNotes
Windowsx64, arm64, ia32Windows 10+
macOSx64, arm64 (Apple Silicon)macOS 10.15+
Linuxx64, arm64, armv7lMost distributions

Project Structure

my-app/
├── src/
│   ├── main/
│   │   ├── main.ts          # Main process
│   │   ├── preload.ts       # Preload scripts
│   │   └── ipc.ts           # IPC handlers
│   ├── renderer/
│   │   ├── index.html
│   │   ├── App.tsx
│   │   └── components/
│   └── shared/
│       └── types.ts
├── resources/
│   ├── icon.icns            # macOS
│   ├── icon.ico             # Windows
│   └── icon.png             # Linux
├── electron-builder.yml
├── package.json
└── forge.config.ts

Main Process

Entry Point

// src/​main/​main.ts
import { app, BrowserWindow, ipcMain } from "electron";
import path from "path";

let mainWindow: BrowserWindow | null = null;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    minWidth: 800,
    minHeight: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
      contextIsolation: true,
      nodeIntegration: false,
      sandbox: true,
    },
    titleBarStyle: "hiddenInset", // macOS
    frame: process.platform === "darwin", // Windows/​Linux custom frame
    show: false, // Show when ready
  });

  // Load the app
  if (process.env.NODE_ENV === "development") {
    mainWindow.loadURL("http://localhost:5173");
    mainWindow.webContents.openDevTools();
  } else {
    mainWindow.loadFile(path.join(__dirname, "../​renderer/​index.html"));
  }

  // Show when ready to prevent flash
  mainWindow.once("ready-to-show", () => {
    mainWindow?.show();
  });

  mainWindow.on("closed", () => {
    mainWindow = null;
  });
}

app.whenReady().then(createWindow);

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

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

Preload Script (Security Bridge)

// src/​main/​preload.ts
import { contextBridge, ipcRenderer } from "electron";

// Expose safe APIs to renderer
contextBridge.exposeInMainWorld("electronAPI", {
  // File operations
  openFile: () => ipcRenderer.invoke("dialog:openFile"),
  saveFile: (content: string) => ipcRenderer.invoke("dialog:saveFile", content),

  // App info
  getVersion: () => ipcRenderer.invoke("app:getVersion"),

  // Window controls
  minimize: () => ipcRenderer.send("window:minimize"),
  maximize: () => ipcRenderer.send("window:maximize"),
  close: () => ipcRenderer.send("window:close"),

  // Two-way communication
  onUpdateAvailable: (callback: () => void) => {
    ipcRenderer.on("update:available", callback);
    return () => ipcRenderer.removeListener("update:available", callback);
  },
});

// Type definitions for renderer
declare global {
  interface Window {
    electronAPI: {
      openFile: () => Promise<string | null>;
      saveFile: (content: string) => Promise<boolean>;
      getVersion: () => Promise<string>;
      minimize: () => void;
      maximize: () => void;
      close: () => void;
      onUpdateAvailable: (callback: () => void) => () => void;
    };
  }
}

IPC Handlers

// src/​main/​ipc.ts
import { ipcMain, dialog, BrowserWindow } from "electron";
import fs from "fs/​promises";

export function setupIPC() {
  // File dialogs
  ipcMain.handle("dialog:openFile", async () => {
    const { canceled, filePaths } = await dialog.showOpenDialog({
      properties: ["openFile"],
      filters: [
        { name: "Text Files", extensions: ["txt", "md"] },
        { name: "All Files", extensions: ["*"] },
      ],
    });

    if (canceled || filePaths.length === 0) return null;
    return fs.readFile(filePaths[0], "utf-8");
  });

  ipcMain.handle("dialog:saveFile", async (_, content: string) => {
    const { canceled, filePath } = await dialog.showSaveDialog({
      filters: [{ name: "Text Files", extensions: ["txt"] }],
    });

    if (canceled || !filePath) return false;
    await fs.writeFile(filePath, content);
    return true;
  });

  // Window controls
  ipcMain.on("window:minimize", (event) => {
    BrowserWindow.fromWebContents(event.sender)?.minimize();
  });

  ipcMain.on("window:maximize", (event) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (win?.isMaximized()) {
      win.unmaximize();
    } else {
      win?.maximize();
    }
  });

  ipcMain.on("window:close", (event) => {
    BrowserWindow.fromWebContents(event.sender)?.close();
  });
}

Renderer Process (React)

Using Exposed APIs

// src/​renderer/​App.tsx
import { useState, useEffect } from "react";

function App() {
  const [version, setVersion] = useState("");
  const [content, setContent] = useState("");

  useEffect(() => {
    window.electronAPI.getVersion().then(setVersion);

    const unsubscribe = window.electronAPI.onUpdateAvailable(() => {
      alert("Update available!");
    });

    return unsubscribe;
  }, []);

  const handleOpen = async () => {
    const fileContent = await window.electronAPI.openFile();
    if (fileContent) {
      setContent(fileContent);
    }
  };

  const handleSave = async () => {
    await window.electronAPI.saveFile(content);
  };

  return (
    <div className="app">
      <header className="titlebar">
        <span>My App v{version}</​span>
        <div className="window-controls">
          <button onClick={window.electronAPI.minimize}>−</​button>
          <button onClick={window.electronAPI.maximize}>□</​button>
          <button onClick={window.electronAPI.close}>×</​button>
        </​div>
      </​header>

      <main>
        <button onClick={handleOpen}>Open File</​button>
        <textarea
          value={content}
          onChange={(e) => setContent(e.target.value)}
        />
        <button onClick={handleSave}>Save File</​button>
      </​main>
    </​div>
  );
}

Custom Title Bar CSS

.titlebar {
  -webkit-app-region: drag; /​* Make draggable */
  height: 32px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 16px;
  background: #1e1e1e;
  color: white;
}

.window-controls {
  -webkit-app-region: no-drag; /​* Buttons clickable */
  display: flex;
  gap: 8px;
}

.window-controls button {
  width: 32px;
  height: 32px;
  border: none;
  background: transparent;
  color: white;
  cursor: pointer;
}

.window-controls button:hover {
  background: rgba(255, 255, 255, 0.1);
}

Native Features

System Tray

import { Tray, Menu, nativeImage } from "electron";

let tray: Tray | null = null;

function createTray() {
  const icon = nativeImage.createFromPath("resources/​tray-icon.png");
  tray = new Tray(icon.resize({ width: 16, height: 16 }));

  const contextMenu = Menu.buildFromTemplate([
    { label: "Show App", click: () => mainWindow?.show() },
    { type: "separator" },
    { label: "Quit", click: () => app.quit() },
  ]);

  tray.setToolTip("My App");
  tray.setContextMenu(contextMenu);

  tray.on("click", () => {
    mainWindow?.show();
  });
}

Native Menus

import { Menu } from "electron";

const template: Electron.MenuItemConstructorOptions[] = [
  {
    label: "File",
    submenu: [
      {
        label: "Open",
        accelerator: "CmdOrCtrl+O",
        click: () => {
          /​* handle */
        },
      },
      {
        label: "Save",
        accelerator: "CmdOrCtrl+S",
        click: () => {
          /​* handle */
        },
      },
      { type: "separator" },
      { role: "quit" },
    ],
  },
  {
    label: "Edit",
    submenu: [
      { role: "undo" },
      { role: "redo" },
      { type: "separator" },
      { role: "cut" },
      { role: "copy" },
      { role: "paste" },
    ],
  },
];

Menu.setApplicationMenu(Menu.buildFromTemplate(template));

Notifications

import { Notification } from "electron";

new Notification({
  title: "Update Available",
  body: "A new version is ready to install.",
  icon: "resources/​icon.png",
}).show();

Auto Updates

import { autoUpdater } from "electron-updater";

autoUpdater.checkForUpdatesAndNotify();

autoUpdater.on("update-available", () => {
  mainWindow?.webContents.send("update:available");
});

autoUpdater.on("update-downloaded", () => {
  autoUpdater.quitAndInstall();
});

Building & Distribution

electron-builder Configuration

# electron-builder.yml
appId: com.mycompany.myapp
productName: My App
copyright: Copyright © 2025

directories:
  output: dist
  buildResources: resources

files:
  - dist/​**/​*
  - package.json

mac:
  category: public.app-category.developer-tools
  target:
    - target: dmg
      arch: [x64, arm64]
    - target: zip
      arch: [x64, arm64]
  hardenedRuntime: true
  gatekeeperAssess: false
  entitlements: build/​entitlements.mac.plist
  notarize: true

win:
  target:
    - target: nsis
      arch: [x64]
  sign: true

linux:
  target:
    - target: AppImage
    - target: deb
    - target: rpm
  category: Development

publish:
  provider: github
  releaseType: release

Build Commands

# Development
npm run dev

# Build for current platform
npm run build

# Build for all platforms
npm run build:all

# Build for specific platform
npm run build:mac
npm run build:win
npm run build:linux

Security Best Practices

DO:

  • Always use contextIsolation: true
  • Use preload scripts for IPC
  • Validate all IPC inputs
  • Enable sandbox: true
  • Sign and notarize for distribution

DON'T:

  • Enable nodeIntegration
  • Use remote module
  • Load untrusted content
  • Expose full Node.js APIs
  • Skip code signing

Alternatives to Consider

FrameworkBest For
TauriSmaller bundle, Rust backend
NeutralinoLightweight, system webview
ElectronFull Node.js, mature ecosystem

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View