Cross-platform builds + GitHub Actions CI/CD + code signing + auto-update.
Build your desktop app. Push to release.
English | νκ΅μ΄
Part of Starter Series β Stop explaining CI/CD to your AI every time. Clone and start.
Docker Deploy Β· Discord Bot Β· Telegram Bot Β· Browser Extension Β· Electron App Β· npm Package Β· React Native Β· VS Code Extension Β· MCP Server Β· Python MCP Server Β· Cloudflare Pages
Via create-starter (recommended):
npx @starter-series/create my-electron-app --template electron-app
cd my-electron-app && npm install && npm startOr clone directly:
git clone https://github.com/starter-series/electron-app-starter my-electron-app
cd my-electron-app && npm install && npm startThen build for your platform:
npm run distβββ src/
β βββ main.js # Main process (BrowserWindow, IPC, auto-update)
β βββ preload.js # Preload script (contextBridge + IPC whitelist)
β βββ system-info.js # Pure handler body for the system-info channel
β βββ shared/
β β βββ ipc-contract.js # Single source of truth for IPC channels + types
β βββ renderer/
β βββ index.html # Renderer HTML
β βββ renderer.js # Renderer logic (consumes window.api)
β βββ styles.css # Minimal styles
βββ assets/
β βββ icon.png # App icon placeholder (replace with yours)
βββ tests/
β βββ app.test.js # Structure tests
β βββ ipc-contract.test.js # Channel contract + preload whitelist
β βββ system-info-handler.test.js # Pure-function handler (DI mocked)
βββ docs/
β βββ CODE_SIGNING.md # macOS + Windows code signing setup
β βββ AUTO_UPDATE.md # electron-updater configuration guide
βββ scripts/
β βββ bump-version.js # Semver version bumper
βββ .github/
β βββ workflows/
β β βββ ci.yml # Lint, test
β β βββ cd.yml # Cross-platform build + GitHub Release
β β βββ setup.yml # Auto setup checklist on first use
β βββ PULL_REQUEST_TEMPLATE.md
βββ eslint.config.js # ESLint v9 flat config
βββ package.json
- Cross-platform β macOS (dmg, zip), Windows (NSIS installer), Linux (AppImage, deb)
- CI Pipeline β Security audit, ESLint, Jest on every push and PR
- CD Pipeline β One-click cross-platform build + GitHub Release via matrix strategy
- Auto-update β
electron-updaterchecks GitHub Releases on startup, downloads and installs automatically - Code signing β Optional macOS notarization + Windows signing via GitHub Secrets
- Security β
contextIsolation: true,nodeIntegration: false,sandbox: true, Content Security Policy - IPC bridge example β Request/response + event subscription demo with a whitelist-based preload (details)
- Version management β
npm run version:patch/minor/major - Template setup β Auto-creates setup checklist issue on first use
| Step | What it does |
|---|---|
| Security audit | npm audit for dependency vulnerabilities |
| Lint | ESLint v9 flat config |
| Test | Jest (passes with no tests by default) |
| Workflow | What it does |
|---|---|
CodeQL (codeql.yml) |
Static analysis for security vulnerabilities (push/PR + weekly) |
Maintenance (maintenance.yml) |
Weekly CI health check β auto-creates issue on failure |
Stale (stale.yml) |
Labels inactive issues/PRs after 30 days, auto-closes after 7 more |
| Step | What it does |
|---|---|
| CI gate | Runs full CI first, build only proceeds if CI passes |
| Version guard | Fails if git tag already exists for this version |
| Matrix build | Builds on macOS, Windows, and Linux in parallel |
| Upload artifacts | Saves all platform builds as GitHub Actions artifacts |
| GitHub Release | Creates a tagged release with all platform binaries attached |
How to release:
- Bump version:
npm run version:patch(orversion:minor/version:major) - Commit and push to
main - Go to Actions tab > Build & Release > Run workflow
- When done, a GitHub Release with all platform builds is created automatically
- Existing users with auto-update receive the new version automatically
Code signing is optional. Builds work without it (apps will be unsigned). See docs/CODE_SIGNING.md for setup details.
| Secret | Description |
|---|---|
CSC_LINK |
Base64-encoded .p12 Developer ID certificate |
CSC_KEY_PASSWORD |
Certificate password |
APPLE_ID |
Apple ID email |
APPLE_APP_SPECIFIC_PASSWORD |
App-specific password for notarization |
APPLE_TEAM_ID |
Apple Developer Team ID |
| Secret | Description |
|---|---|
CSC_LINK |
Base64-encoded .pfx code signing certificate |
CSC_KEY_PASSWORD |
Certificate password |
# Run the app
npm start
# Run with logging enabled
npm run dev
# Bump version (updates package.json)
npm run version:patch # 1.0.0 β 1.0.1
npm run version:minor # 1.0.0 β 1.1.0
npm run version:major # 1.0.0 β 2.0.0
# Build for current platform
npm run dist
# Build for specific platform
npm run dist:mac
npm run dist:win
npm run dist:linux
# Lint & test
npm run lint
npm testThe starter ships with a working IPC bridge that covers the two patterns real Electron apps need. All channel names live in src/shared/ipc-contract.js β the main process and the preload both read from it so the whitelist can never drift from the handler table.
1. Request / response β ipcRenderer.invoke β ipcMain.handle
// src/preload.js β whitelist-enforced API on window.api
contextBridge.exposeInMainWorld('api', {
getSystemInfo() {
assertAllowed(invokeAllowed, 'system-info');
return ipcRenderer.invoke('system-info');
},
// ...
});// src/main.js β pure handler, testable without Electron
ipcMain.handle('system-info', () =>
buildSystemInfo({ os, electronApp: app, process }),
);2. Event subscription β webContents.send β ipcRenderer.on
// src/preload.js β returns an unsubscribe function
onPowerEvent(callback) {
const listener = (_e, payload) => callback(payload);
ipcRenderer.on('power-event', listener);
return () => ipcRenderer.removeListener('power-event', listener);
}// src/main.js β fan out native powerMonitor events
powerMonitor.on('suspend', () => broadcast('suspend'));
powerMonitor.on('resume', () => broadcast('resume'));Renderer usage (src/renderer/renderer.js):
window.api.getSystemInfo().then(renderInfoBlock);
const off = window.api.onPowerEvent(renderLogLine);
window.addEventListener('beforeunload', off); // always unsubscribeSecurity stance β the preload never exposes ipcRenderer itself, only the specific methods above, and rejects any channel that's not on the whitelist. The BrowserWindow runs with contextIsolation: true, nodeIntegration: false, sandbox: true, and a strict CSP (default-src 'self'). See Electron's Context Isolation docs for the threat model this protects against.
Electron Forge and electron-vite are excellent toolchains that manage Electron's build complexity. This template takes a different approach:
| This template | Forge / electron-vite | |
|---|---|---|
| Philosophy | Thin starter with CI/CD | Full toolchain with plugins |
| Build system | electron-builder (config in package.json) | Forge makers/publishers or Vite |
| CI/CD | Full pipeline with matrix builds + auto-update | Not included |
| Code signing | GitHub Secrets setup guide included | Manual setup |
| Auto-update | Works out of the box with GitHub Releases | Manual configuration |
| Dependencies | 1 runtime, 6 dev | 50+ |
| AI/vibe-coding | LLMs generate clean vanilla JS | LLMs must understand plugin system |
Choose this template if:
- You want production CI/CD and auto-update from day one
- You need cross-platform builds with code signing via GitHub Actions
- You're using AI tools to generate app code
- Your app is utility-scale, not a complex framework app
Choose Forge/electron-vite if:
- You need React/Vue/Svelte with HMR in the renderer
- You want the Forge plugin ecosystem
- Your app has complex native module requirements
This template uses vanilla JavaScript. If you need TypeScript:
- Add
typescriptto devDependencies - Add a
tsconfig.json - Rename
.jsfiles to.ts - Update ESLint config for TypeScript
PRs welcome. Please use the PR template.