| name | electron |
| description | Electron patterns for building secure, cross-platform desktop applications. Trigger: When building desktop apps, working with Electron main/renderer processes, IPC communication, native integrations, packaging, or auto-updates.
|
| metadata | {"author":"gentleman-programming","version":"2.0","source":"https://github.com/Gentleman-Programming/Gentleman-Skills/tree/main/community/electron","scope":"general-purpose"} |
When to Use
Load this skill when:
- Building cross-platform desktop applications
- Working with Electron main and renderer processes
- Implementing IPC (Inter-Process Communication)
- Integrating native OS features (menus, notifications, dialogs, file system)
- Choosing or implementing a renderer UI framework (React, Vue, Svelte, SvelteKit)
- Setting up auto-updates, packaging, and distribution
Critical Patterns
Pattern 1: Project Structure
src/
|-- main/ # Main process (Node.js)
| |-- index.ts # Entry point
| |-- ipc/ # IPC handlers
| | |-- handlers.ts
| | `-- channels.ts
| |-- services/ # Native services (store, updater)
| `-- windows/ # Window management
|-- preload/ # Safe bridge API for renderer
| `-- index.ts
|-- shared/ # Shared types/contracts
| `-- ipc.ts
`-- renderer/ # UI app (framework of choice)
|-- src/
| |-- App.(tsx|vue|svelte)
| `-- lib/
`-- index.html
Pattern 2: Secure IPC Communication
Always use contextBridge for secure communication:
import { contextBridge, ipcRenderer } from 'electron';
import type { ElectronAPI, IpcChannels } from '../shared/ipc';
const electronAPI: ElectronAPI = {
send: <T extends keyof IpcChannels>(channel: T, data: IpcChannels[T]['request']) => {
ipcRenderer.send(channel, data);
},
invoke: <T extends keyof IpcChannels>(
channel: T,
data: IpcChannels[T]['request']
): Promise<IpcChannels[T]['response']> => {
return ipcRenderer.invoke(channel, data);
},
on: <T extends keyof IpcChannels>(
channel: T,
callback: (data: IpcChannels[T]['response']) => void
) => {
const subscription = (_: Electron.IpcRendererEvent, data: IpcChannels[T]['response']) => {
callback(data);
};
ipcRenderer.on(channel, subscription);
return () => ipcRenderer.removeListener(channel, subscription);
}
};
contextBridge.exposeInMainWorld('electron', electronAPI);
Pattern 3: Type-Safe IPC Contracts
Define all channels with request/response types:
export interface IpcChannels {
'app:get-version': {
request: void;
response: string;
};
'file:read': {
request: { path: string };
response: { content: string } | { error: string };
};
'file:write': {
request: { path: string; content: string };
response: { success: boolean };
};
'dialog:open-file': {
request: { filters?: Electron.FileFilter[] };
response: string | null;
};
'store:get': {
request: { key: string };
response: unknown;
};
'store:set': {
request: { key: string; value: unknown };
response: void;
};
}
export type ElectronAPI = {
send<T extends keyof >(: T, : [T][]): ;
invoke<T keyof >(
: T,
: [T][]
): <[T][]>;
on<T keyof >(
: T,
:
): ;
};
{
{
: ;
}
}
Code Examples
Example 1: Main Process Setup
import { app, BrowserWindow } from 'electron';
import path from 'path';
import { registerIpcHandlers } from './ipc/handlers';
let mainWindow: BrowserWindow | null = null;
async function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true
}
});
registerIpcHandlers();
if (process.env.NODE_ENV === 'development') {
await mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools({ mode: 'detach' });
} {
mainWindow.(path.(__dirname, ));
}
mainWindow.(, {
mainWindow = ;
});
}
app.().(createWindow);
app.(, {
(process. !== ) app.();
});
app.(, {
(.(). === ) ();
});
Example 2: IPC Handlers
import { app, dialog, ipcMain } from 'electron';
import fs from 'node:fs/promises';
import Store from 'electron-store';
const store = new Store();
export function registerIpcHandlers() {
ipcMain.handle('app:get-version', () => app.getVersion());
ipcMain.handle('file:read', async (_, { path }) => {
try {
const content = await fs.readFile(path, 'utf-8');
return { content };
} catch (error) {
return { error: (error as Error).message };
}
});
ipcMain.handle('file:write', async (_, { path, content }) => {
try {
await fs.writeFile(path, content, 'utf-8');
return { success: true };
} catch {
return { success: };
}
});
ipcMain.(, (_, { filters }) => {
result = dialog.({
: [],
: filters || [{ : , : [] }]
});
result. ? : result.[];
});
ipcMain.(, store.(key));
ipcMain.(, {
store.(key, value);
});
}
Example 3: Renderer Integration Choices
React hook:
import { useEffect, useState } from 'react';
export function useAppVersion() {
const [version, setVersion] = useState<string | null>(null);
useEffect(() => {
window.electron.invoke('app:get-version', undefined).then(setVersion);
}, []);
return version;
}
Vue composable:
import { onMounted, ref } from 'vue';
export function useAppVersion() {
const version = ref<string | null>(null);
onMounted(async () => {
version.value = await window.electron.invoke('app:get-version', undefined);
});
return { version };
}
SvelteKit runes module:
export function createVersionQuery() {
const state = $state({ value: null as string | null, loading: false });
const run = async () => {
state.loading = true;
state.value = await window.electron.invoke('app:get-version', undefined);
state.loading = false;
};
return { state, run };
}
Example 4: Auto-Updater Setup
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import type { BrowserWindow } from 'electron';
export function setupAutoUpdater(mainWindow: BrowserWindow) {
autoUpdater.logger = log;
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
autoUpdater.on('checking-for-update', () => mainWindow.webContents.send('updater:checking'));
autoUpdater.on('update-available', (info) => mainWindow.webContents.send('updater:available', info));
autoUpdater.on('update-not-available', () => mainWindow.webContents.send('updater:not-available'));
autoUpdater.on('download-progress', (progress) => mainWindow.webContents.send('updater:progress', progress));
autoUpdater.(, mainWindow..());
autoUpdater.(, mainWindow..(, error.));
( {
autoUpdater.();
}, );
}
Example 5: Native Menu Setup
import { app, BrowserWindow, Menu, shell } from 'electron';
export function createMenu(mainWindow: BrowserWindow) {
const isMac = process.platform === 'darwin';
const template: Electron.MenuItemConstructorOptions[] = [
...(isMac
? [{ label: app.name, submenu: [{ role: 'about' }, { type: 'separator' }, { role: 'quit' }] }]
: []),
{
label: 'File',
submenu: [
{
label: 'Open File',
accelerator: 'CmdOrCtrl+O',
click: () => mainWindow.webContents.send('menu:open-file')
},
{ type: 'separator' },
isMac ? { role: 'close' } : { role: 'quit' }
]
},
{
label: 'Help',
submenu: [{ label: 'Documentation', : shell.() }]
}
];
.(.(template));
}
Anti-Patterns
Do not enable nodeIntegration
const win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
const winSafe = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true
}
});
Do not use deprecated remote module
const { BrowserWindow } = require('@electron/remote');
const path = await window.electron.invoke('dialog:open-file', {});
Do not expose raw ipcRenderer to renderer
contextBridge.exposeInMainWorld('electron', { ipcRenderer });
contextBridge.exposeInMainWorld('electron', {
invoke: (channel: string, data: unknown) => {
const allowedChannels = ['app:get-version', 'file:read'];
if (!allowedChannels.includes(channel)) {
throw new Error(`Channel ${channel} not allowed`);
}
return ipcRenderer.invoke(channel, data);
}
});
Quick Reference
| Task | Pattern |
|---|
| Create project | npm create electron-vite@latest |
| Main process file access | Use Node.js fs module in main |
| Renderer file access | IPC through preload |
| Persistent storage | electron-store in main process |
| Auto-updates | electron-updater |
| Native notifications | new Notification() in main |
| System tray | Tray class in main |
| Keyboard shortcuts | globalShortcut.register() |
| Deep linking | app.setAsDefaultProtocolClient() |
| Code signing | electron-builder config |
Resources