electron — Cursor rule: secure Electron best practices
A Cursor rule that pins down how your team writes Electron code: context isolation, safe contextBridge APIs, request-response IPC and cross-platform paths.
.cursor/rules/electron.mdcWhat electron does
The rule attaches to Cursor and applies to **/*.{js,jsx} files. It assumes Electron 28 or newer, Electron Forge for scaffolding, and a Vite + TypeScript template. File names use hyphens, not underscores.
Security is stated as a requirement, not a suggestion. contextIsolation stays on, sandbox: true is recommended, and anything exposed to the renderer goes through contextBridge.exposeInMainWorld as a named function with argument checks. Handing ipcRenderer.send straight to the window is called out as a mistake, since the renderer can then fire arbitrary IPC messages. For calls that need an answer, the rule wants ipcMain.handle plus ipcRenderer.invoke instead of the send/on pair, which is prone to races.
The rest covers details that tend to slip: a strict CSP in index.html, .mjs extensions for ESM in main and preload scripts, awaiting imports before app.whenReady(), and path.join or os.tmpdir() rather than string concatenation. Linting and tests live in package.json scripts and run from a pre-commit hook. One catch worth knowing up front: sandboxed preload scripts cannot use ESM imports, so external modules have to be bundled.
What it helps with
- stop the renderer from sending arbitrary IPC messages
- remove race conditions in renderer-to-main requests
- build file paths that work on Windows and macOS alike
- add a strict CSP without breaking script loading
- move a project to ESM on Electron 28+
Example prompts for the agent
- Check the preload script — are we exposing more than we should through contextBridge?
- Rewrite this data request using ipcMain.handle and ipcRenderer.invoke
- Replace the string concatenation with path.join and os.tmpdir()
How to install electron
Manually: put the files in place
.cursor/rules/electron.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/electron.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/electron.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/electron.mdManually: put the files in place
.clinerules/electron.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about electron
How do I install the electron rule in Cursor?
Create .cursor/rules/electron.mdc at the root of your project and paste the rule contents into it.
What does the electron rule do?
It sets requirements for Electron code: context isolation, safe APIs through contextBridge, request-response IPC, cross-platform paths and one consistent build toolchain.
Which files does the rule apply to?
JavaScript and JSX files matching **/*.{js,jsx}.
Source text
electron.mdc · CC0-1.0
---
description: This guide provides opinionated, actionable best practices for building secure, performant, and maintainable Electron applications using modern patterns and consistent tooling.
globs: **/*.{js,jsx}
---
# Electron Best Practices
Electron development demands a disciplined approach to security, performance, and maintainability. This guide outlines the definitive best practices for our team, leveraging modern Electron features and tooling (targeting Electron 28+).
## 1. Project Setup & Structure
Always start with **Electron Forge** to standardize project structure, build pipelines, and stay aligned with the latest Electron APIs.
- **Scaffolding:** Use a modern template like Vite + TypeScript.
❌ BAD: Manual setup, outdated CLIs.
✅ GOOD:
```bash
npx create-electron-app@latest my-app --template=vite-typescript
```
- **File Naming:** Adhere to Electron's coding style for JavaScript files.
❌ BAD: `my_module.js`
✅ GOOD: `my-module.js`
## 2. Security Fundamentals (Non-Negotiable)
Security is paramount. Always enable context isolation and expose APIs safely.
- **Context Isolation (Mandatory):** Keep `contextIsolation` enabled. It's on by default since Electron 12.
❌ BAD: `new BrowserWindow({ webPreferences: { contextIsolation: false } })`
✅ GOOD: (Default behavior, no explicit setting needed unless overriding)
```javascript
// main.mjs
new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.mjs'),
sandbox: true // Strongly recommended
}
})
```
- **Safe API Exposure with `contextBridge`:** Never mutate the global `window` object directly. Use `contextBridge.exposeInMainWorld` and filter arguments.
❌ BAD: Exposing `ipcRenderer.send` directly.
```javascript
// preload.mjs
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('myAPI', {
send: ipcRenderer.send // ❌ Allows renderer to send arbitrary IPC messages
});
```
✅ GOOD: Expose specific, argument-filtered functions.
```javascript
// preload.mjs
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs'),
saveSettings: (settings) => {
// ✅ Validate and filter arguments before sending
if (typeof settings === 'object' && settings !== null) {
ipcRenderer.send('save-settings', settings);
} else {
console.error('Invalid settings object provided.');
}
}
});
// interface.d.ts (for TypeScript)
declare global {
interface Window {
electronAPI: {
loadPreferences: () => Promise<any>;
saveSettings: (settings: object) => void;
};
}
}
```
- **Content Security Policy (CSP):** Implement a strict CSP in your `index.html` or via `webRequest.onHeadersReceived`.
✅ GOOD:
```html
<!-- index.html -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
```
## 3. ES Modules (ESM) Adoption (Electron 28+)
Leverage native ESM for cleaner, more modern code.
- **Main Process:** Use `.mjs` extension or `"type": "module"` in `package.json`.
- **`await` for Pre-Ready APIs:** ESM imports are asynchronous. Ensure critical APIs (e.g., `app.setPath`) are `await`ed before `app.whenReady()`.
❌ BAD:
```javascript
// main.mjs
import './setup-paths.mjs'; // May resolve after app is ready
app.whenReady().then(() => { /* ... */ });
```
✅ GOOD:
```javascript
// main.mjs
import { app } from 'electron';
await import('./setup-paths.mjs'); // Guarantees execution before ready
app.whenReady().then(() => { /* ... */ });
```
- **Preload Scripts:** Always use the `.mjs` extension for ESM preload scripts.
- **Sandboxed Preloads:** Cannot use ESM imports. Bundle them if external modules are needed.
- **Context Isolation:** Required for dynamic Node.js ESM imports in unsandboxed preloads.
❌ BAD: `preload.js` with `import` statements.
✅ GOOD: `preload.mjs`
## 4. IPC Communication
Use `ipcMain.handle` and `ipcRenderer.invoke` for explicit request-response patterns.
- **Request-Response:**
❌ BAD: Using `ipcRenderer.send` for requests that expect a response.
```javascript
// renderer.js
ipcRenderer.send('get-data', someId);
ipcRenderer.on('data-response', (event, data) => { /* ... */ }); // Race condition prone
```
✅ GOOD:
```javascript
// main.mjs
ipcMain.handle('get-data', async (event, someId) => {
// ✅ Perform validation, access native APIs
return await fetchData(someId);
});
// renderer.js
const data = await window.electronAPI.getData(someId); // Assuming exposed via contextBridge
```
## 5. System Path Handling
Always use Node.js `path` and `os` modules for cross-platform compatibility.
- **File Paths:** Use `path.join()` for concatenation.
❌ BAD: `app.getPath('userData') + '/config.json'`
✅ GOOD:
```javascript
import path from 'node:path';
import { app } from 'electron';
const configPath = path.join(app.getPath('userData'), 'config.json');
```
- **Temporary Directories:** Use `os.tmpdir()`.
❌ BAD: `'/tmp/my-app-data'`
✅ GOOD:
```javascript
import os from 'node:os';
const tempDir = os.tmpdir();
```
## 6. Testing & Linting
Integrate Electron's built-in tooling for consistent code quality.
- **Linting:** Run `npm run lint` regularly and integrate into pre-commit hooks.
✅ GOOD: Ensure your `package.json` includes:
```json
"scripts": {
"lint": "electron-builder lint" // Or specific linter like 'eslint .'
}
```
- **Unit Tests:** Add new tests for any changes or new features.
✅ GOOD: `npm run test`
```json
"scripts": {
"test": "electron-mocha spec" // Example with electron-mocha
}
```
## 7. Staying Current
Electron evolves rapidly. Proactively manage updates and breaking changes.
- **Official Documentation:** Always consult the version-specific official documentation.
- **Breaking Changes:** Regularly review the "Breaking Changes" page for each major Electron release to anticipate necessary updates.
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc