npx skills add ...
npx skills add jwynia/agent-skills --skill electron-best-practices
Guide AI agents through Electron app development with React including security patterns, type-safe IPC, React integration, packaging with code signing, and testing. Keywords: electron, electron-vite, electron-forge, contextBridge, IPC, security, react, packaging, code signing, notarization, playwright, desktop app.
npx skills add jwynia/agent-skills --skill electron-best-practices
Guide AI agents in building secure, production-ready Electron applications with React. This skill provides security patterns, type-safe IPC communication, project setup guidance, packaging and code signing workflows, and tools for analysis, scaffolding, and type generation.
Use this skill when:
Do NOT use this skill when:
Modern Electron security relies on three defaults that became standard in Electron 20+: context isolation, sandbox mode, and nodeIntegration disabled. Disabling any of them allows XSS attacks to escalate to full remote code execution. All main-renderer communication must flow through contextBridge:
Set Content Security Policy via HTTP headers for apps loading local files, restricting script sources to 'self'.
The invoke/handle pattern is preferred over send/on for request-response communication, providing proper async/await semantics and error propagation. For typed channels, use a mapped type pattern:
For complex applications, electron-trpc provides full type safety using tRPC's router pattern with Zod validation:
Error handling across the IPC boundary requires attention because Electron only serializes the message property of Error objects. Wrap responses in a { success, data, error } result type to preserve full error context.
The recommended stack uses electron-vite for development and Electron Forge for packaging. electron-vite provides a unified configuration managing main, preload, and renderer processes with sub-second dev server startup and instant HMR. Electron Forge uses first-party Electron packages for signing and notarization.
React 18's concurrent features work normally in Electron's Chromium-based renderer. Strict Mode's double-invocation of effects catches IPC listener leaks that would otherwise cause memory issues. Always return cleanup functions from effects that register IPC listeners:
For multi-window applications, the main process should serve as the single source of truth for shared state. Use electron-store for persistence combined with IPC broadcasting so any window's mutation updates all others.
| Category | Prefer | Avoid |
|---|---|---|
| Security | contextBridge.exposeInMainWorld() | nodeIntegration: true |
| IPC | invoke/handle pattern | send/on for request-response |
| Preload | Typed function wrappers | Exposing raw ipcRenderer |
| Build tool | electron-vite | webpack-based toolchains |
| Packaging | Electron Forge | Manual packaging |
| State | Zustand + electron-store | Redux for simple apps |
| Testing | Playwright E2E | Spectron (deprecated) |
| Updates | electron-updater | Manual update checks |
| Signing | CI-integrated code signing | Unsigned releases |
| CSP | HTTP headers, 'self' only | No CSP |
| Error handling | Result type {success, data, error} | Raw Error across IPC |
| Multi-window | Main process as state hub | Direct window-to-window |
When generating Electron code, follow these patterns:
Always enable contextIsolation and sandbox. Never enable nodeIntegration. The preload path must resolve to the built output location.
Group related handlers into modules. Use the result type pattern for all return values. Validate all arguments received from the renderer process.
Avoid these patterns when generating Electron code:
| Anti-Pattern | Problem | Solution |
|---|---|---|
nodeIntegration: true | XSS escalates to full RCE | Keep disabled (default) |
Exposing ipcRenderer directly | Full IPC access from renderer | Wrap in contextBridge functions |
Missing contextIsolation | Renderer accesses preload scope | Keep enabled (default since Electron 12) |
| No code signing | OS security warnings, Gatekeeper blocks | Sign and notarize for all platforms |
BrowserWindow without sandbox | Preload has full Node.js access | Enable sandbox (default since Electron 20) |
| Unvalidated IPC arguments | Injection attacks from renderer | Validate with Zod or manual checks |
0.0.0.0 server binding | Network-exposed local server | Always bind to 127.0.0.1 |
| Missing CSP headers | Script injection vectors | Set strict CSP via HTTP headers |
| No IPC error serialization | Lost error context across boundary | Use Result type pattern |
| Spectron for testing | Deprecated, Electron 13 max | Use Playwright |
See references/security/security-checklist.md for the full security audit checklist.
Analyze Electron projects for security misconfigurations:
Scaffold a new Electron + React project with secure defaults:
Generate TypeScript type definitions from IPC handler files:
references/security/context-isolation.md - contextBridge and isolation patternsreferences/security/csp-and-permissions.md - Content Security Policy configurationreferences/security/security-checklist.md - Full security audit checklistreferences/ipc/typed-ipc.md - Typed channel map patternsreferences/ipc/electron-trpc.md - tRPC integration for full type safetyreferences/ipc/error-serialization.md - Result types across IPC boundaryreferences/architecture/project-structure.md - Directory organizationreferences/architecture/process-separation.md - Main, preload, and renderer rolesreferences/architecture/multi-window-state.md - Shared state across windowsreferences/integration/react-patterns.md - useEffect cleanup, Strict Modereferences/integration/state-management.md - Zustand and electron-store patternsreferences/packaging/code-signing.md - Platform-specific signing workflowsreferences/packaging/auto-updates.md - electron-updater configurationreferences/packaging/bundle-optimization.md - Size reduction techniquesreferences/packaging/ci-cd-patterns.md - GitHub Actions matrix buildsreferences/testing/playwright-e2e.md - Playwright Electron supportreferences/testing/unit-testing.md - Jest/Vitest multi-project configurationreferences/testing/test-structure.md - Test organization patternsreferences/tooling/electron-vite.md - Build tool configurationreferences/tooling/electron-forge.md - Packaging and distributionreferences/tooling/tauri-comparison.md - When to choose Tauri insteadassets/templates/main-process.ts.md - Main process starter templateassets/templates/preload-script.ts.md - Preload script with contextBridgeassets/templates/ipc-handler.ts.md - IPC handler module templateassets/templates/react-root.tsx.md - React root component templateassets/configs/electron-vite.config.ts.md - electron-vite configurationassets/configs/forge.config.js.md - Electron Forge configurationassets/configs/tsconfig.json.md - TypeScript configuration presetsassets/configs/playwright.config.ts.md - Playwright Electron test configassets/examples/typed-ipc-example.md - End-to-end typed IPC walkthroughassets/examples/multi-window-example.md - Multi-window state managementtype IpcChannelMap = {
'load-prefs': { args: []; return: UserPreferences };
'save-file': { args: [content: string]; return: { success: boolean } };
};export const appRouter = t.router({
greeting: t.procedure
.input(z.object({ name: z.string() }))
.query(({ input }) => `Hello, ${input.name}!`),
});src/
├── main/ # Main process (Node.js environment)
│ ├── index.ts
│ └── ipc/ # IPC handlers
├── preload/ # Secure bridge via contextBridge
│ ├── index.ts
│ └── index.d.ts # TypeScript declarations for exposed APIs
└── renderer/ # React application (pure web, no Node access)
├── src/
└── index.htmluseEffect(() => {
const cleanup = window.electronAPI.onUpdateCounter((value) => {
setCount(value);
});
return cleanup;
}, []);const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, '../preload/index.js'),
contextIsolation: true,
sandbox: true,
nodeIntegration: false,
},
});export function registerFileHandlers(): void {
ipcMain.handle('save-file', async (_event, content: string) => {
try {
await fs.writeFile(filePath, content);
return { success: true, data: filePath };
} catch (err) {
return { success: false, error: (err as Error).message };
}
});
}deno run --allow-read scripts/analyze-security.ts <path> [options]
Options:
--strict Enable all checks
--json Output JSON for CI
-h, --help Show help
Examples:
# Analyze a project
deno run --allow-read scripts/analyze-security.ts ./src
# Strict mode for CI pipeline
deno run --allow-read scripts/analyze-security.ts ./src --strict --jsondeno run --allow-read --allow-write scripts/scaffold-electron-app.ts [options]
Options:
--name <name> App name (required)
--path <path> Target directory (default: ./)
--with-react Include React setup
--with-trpc Include electron-trpc
--with-tests Include Playwright tests
Examples:
# Basic app with React
deno run --allow-read --allow-write scripts/scaffold-electron-app.ts \
--name "my-app" --with-react
# Full setup with trpc and tests
deno run --allow-read --allow-write scripts/scaffold-electron-app.ts \
--name "my-app" --with-react --with-trpc --with-testsdeno run --allow-read --allow-write scripts/generate-ipc-types.ts [options]
Options:
--handlers <path> Path to IPC handler files
--output <path> Output path for type definitions
--validate Validate existing types match handlers
Examples:
# Generate types from handlers
deno run --allow-read --allow-write scripts/generate-ipc-types.ts \
--handlers ./src/main/ipc --output ./src/preload/ipc-types.d.ts
# Validate types in CI
deno run --allow-read scripts/generate-ipc-types.ts \
--handlers ./src/main/ipc --validate