- Introduction
- Installation & Setup
- Project Structure
- Main Process vs Renderer Process
- Creating Windows
- IPC Communication
- Menu & Tray
- File System & Dialogs
- Context Bridge & Security
- Notifications
- Auto Updates
- Native Features
- Debugging
- Build & Distribution
- Performance Optimization
- Advanced Patterns
- Electron with React
- Electron with Next.js
Electron is a framework for building cross-platform desktop applications using web technologies (HTML, CSS, JavaScript). It combines Chromium and Node.js into a single runtime.
- Main Process: Backend Node.js process (one per app)
- Renderer Process: Frontend browser window (one per window)
- IPC: Inter-Process Communication between main and renderer
npm install --save-dev electron{
"name": "my-electron-app",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"dev": "electron . --inspect=5858"
},
"devDependencies": {
"electron": "^28.0.0"
}
}npm init -y
npm install --save-dev electronmy-electron-app/
├── main.js # Main process
├── preload.js # Preload script
├── index.html # Entry HTML
├── renderer.js # Renderer script
├── package.json
├── assets/
│ └── icon.png
└── src/
├── main/
│ ├── ipc-handlers.js
│ └── menu.js
└── renderer/
├── index.html
└── renderer.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true
}
});
mainWindow.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});// Runs in browser context
document.getElementById('btn').addEventListener('click', () => {
console.log('Button clicked!');
});const { BrowserWindow } = require('electron');
const win = new BrowserWindow({
width: 1000,
height: 800,
title: 'My App'
});
win.loadURL('https://example.com');
// or
win.loadFile('index.html');const win = new BrowserWindow({
width: 800,
height: 600,
minWidth: 400,
minHeight: 300,
maxWidth: 1200,
maxHeight: 900,
resizable: true,
movable: true,
minimizable: true,
maximizable: true,
closable: true,
focusable: true,
alwaysOnTop: false,
fullscreen: false,
kiosk: false,
frame: true, // Window frame
show: false, // Show later for smooth loading
backgroundColor: '#FFF',
transparent: false,
opacity: 1.0,
icon: path.join(__dirname, 'assets/icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});win.on('ready-to-show', () => {
win.show();
});
win.on('closed', () => {
win = null;
});
win.on('blur', () => console.log('Window lost focus'));
win.on('focus', () => console.log('Window gained focus'));
win.on('maximize', () => console.log('Window maximized'));
win.on('minimize', () => console.log('Window minimized'));
win.on('restore', () => console.log('Window restored'));win.setTitle('New Title');
win.center();
win.maximize();
win.minimize();
win.restore();
win.close();
win.destroy();
win.reload();
win.setFullScreen(true);
win.setBounds({ x: 100, y: 100, width: 800, height: 600 });
win.setSize(1024, 768);
win.setPosition(100, 100);const win = new BrowserWindow({
frame: false,
titleBarStyle: 'hidden', // macOS only
transparent: true
});const parent = new BrowserWindow();
const child = new BrowserWindow({
parent: parent,
modal: true
});Main Process:
const { ipcMain } = require('electron');
// Send to specific window
mainWindow.webContents.send('channel-name', 'data');
// Send to all windows
BrowserWindow.getAllWindows().forEach(win => {
win.webContents.send('channel-name', 'data');
});Preload:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
onChannelName: (callback) => ipcRenderer.on('channel-name', callback)
});Renderer:
window.electron.onChannelName((event, data) => {
console.log(data);
});Main Process:
const { ipcMain } = require('electron');
ipcMain.handle('get-data', async (event, arg) => {
const result = await someAsyncOperation(arg);
return result;
});
ipcMain.on('sync-message', (event, arg) => {
console.log(arg);
event.reply('sync-reply', 'Response data');
});Preload:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
getData: (arg) => ipcRenderer.invoke('get-data', arg),
sendMessage: (arg) => ipcRenderer.send('sync-message', arg),
onReply: (callback) => ipcRenderer.on('sync-reply', callback)
});Renderer:
// Async invoke
const result = await window.api.getData('argument');
// One-way send
window.api.sendMessage('Hello from renderer');
window.api.onReply((event, data) => {
console.log(data);
});Request-Response Pattern:
// Main
ipcMain.handle('fetch-user', async (event, userId) => {
const user = await database.getUser(userId);
return user;
});
// Renderer
const user = await window.api.fetchUser(123);Event Streaming:
// Main
let interval;
ipcMain.on('start-stream', (event) => {
interval = setInterval(() => {
event.sender.send('stream-data', { timestamp: Date.now() });
}, 1000);
});
ipcMain.on('stop-stream', () => {
clearInterval(interval);
});const { Menu } = require('electron');
const template = [
{
label: 'File',
submenu: [
{
label: 'Open',
accelerator: 'CmdOrCtrl+O',
click: () => { console.log('Open clicked'); }
},
{
label: 'Save',
accelerator: 'CmdOrCtrl+S',
click: () => { console.log('Save clicked'); }
},
{ type: 'separator' },
{ role: 'quit' }
]
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
]
},
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);const { Menu, MenuItem } = require('electron');
const contextMenu = new Menu();
contextMenu.append(new MenuItem({ label: 'Cut', role: 'cut' }));
contextMenu.append(new MenuItem({ label: 'Copy', role: 'copy' }));
contextMenu.append(new MenuItem({ label: 'Paste', role: 'paste' }));
// In renderer (via IPC)
window.addEventListener('contextmenu', (e) => {
e.preventDefault();
window.api.showContextMenu();
});
// Main process
ipcMain.on('show-context-menu', (event) => {
const template = [
{ label: 'Item 1', click: () => { console.log('Item 1 clicked'); } },
{ type: 'separator' },
{ label: 'Item 2', click: () => { console.log('Item 2 clicked'); } }
];
const menu = Menu.buildFromTemplate(template);
menu.popup(BrowserWindow.fromWebContents(event.sender));
});const { Tray, Menu, nativeImage } = require('electron');
let tray = null;
app.whenReady().then(() => {
const icon = nativeImage.createFromPath(path.join(__dirname, 'icon.png'));
tray = new Tray(icon.resize({ width: 16, height: 16 }));
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show App', click: () => { mainWindow.show(); } },
{ label: 'Quit', click: () => { app.quit(); } }
]);
tray.setToolTip('My Electron App');
tray.setContextMenu(contextMenu);
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
});
});const { dialog } = require('electron');
// Open File Dialog
const openFile = async () => {
const result = await dialog.showOpenDialog({
title: 'Select a file',
defaultPath: app.getPath('documents'),
buttonLabel: 'Open',
filters: [
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] },
{ name: 'Videos', extensions: ['mkv', 'avi', 'mp4'] },
{ name: 'All Files', extensions: ['*'] }
],
properties: ['openFile', 'multiSelections']
});
if (!result.canceled) {
console.log(result.filePaths);
}
};
// Save Dialog
const saveFile = async () => {
const result = await dialog.showSaveDialog({
title: 'Save file',
defaultPath: path.join(app.getPath('documents'), 'file.txt'),
buttonLabel: 'Save',
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'All Files', extensions: ['*'] }
]
});
if (!result.canceled) {
console.log(result.filePath);
// Write file here
}
};
// Message Box
const showMessage = async () => {
const result = await dialog.showMessageBox({
type: 'info', // 'none', 'info', 'error', 'question', 'warning'
title: 'Title',
message: 'Main message',
detail: 'Additional details',
buttons: ['OK', 'Cancel'],
defaultId: 0,
cancelId: 1
});
console.log(result.response); // Button index
};
// Error Box (synchronous)
dialog.showErrorBox('Error Title', 'Error message');const fs = require('fs').promises;
const path = require('path');
// Read file
ipcMain.handle('read-file', async (event, filePath) => {
try {
const data = await fs.readFile(filePath, 'utf-8');
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
});
// Write file
ipcMain.handle('write-file', async (event, filePath, content) => {
try {
await fs.writeFile(filePath, content, 'utf-8');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Get app paths
const userDataPath = app.getPath('userData');
const documentsPath = app.getPath('documents');
const desktopPath = app.getPath('desktop');
const downloadsPath = app.getPath('downloads');
const tempPath = app.getPath('temp');// preload.js
const { contextBridge, ipcRenderer } = require('electron');
// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('api', {
// Invoke methods
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (content) => ipcRenderer.invoke('dialog:saveFile', content),
// Send methods (one-way)
minimize: () => ipcRenderer.send('window:minimize'),
maximize: () => ipcRenderer.send('window:maximize'),
close: () => ipcRenderer.send('window:close'),
// Receive methods (from main to renderer)
onUpdateDownloaded: (callback) => {
ipcRenderer.on('update:downloaded', callback);
},
// Remove listener
removeListener: (channel) => {
ipcRenderer.removeAllListeners(channel);
}
});
// Expose selective Node.js APIs
contextBridge.exposeInMainWorld('node', {
platform: process.platform,
version: process.version
});const win = new BrowserWindow({
webPreferences: {
// ✅ REQUIRED SECURITY SETTINGS
nodeIntegration: false, // Don't integrate Node.js in renderer
contextIsolation: true, // Isolate preload script context
sandbox: true, // Enable Chromium sandbox
// ✅ RECOMMENDED SETTINGS
webSecurity: true, // Enable web security
allowRunningInsecureContent: false,
// Preload script
preload: path.join(__dirname, 'preload.js'),
// ✅ DISABLE DANGEROUS FEATURES
enableRemoteModule: false, // Remote module is deprecated
nodeIntegrationInWorker: false,
nodeIntegrationInSubFrames: false
}
});
// ✅ Validate navigation
win.webContents.on('will-navigate', (event, navigationUrl) => {
const parsedUrl = new URL(navigationUrl);
if (parsedUrl.origin !== 'https://example.com') {
event.preventDefault();
}
});
// ✅ Prevent new window creation
win.webContents.setWindowOpenHandler(({ url }) => {
// Only allow specific URLs
if (url.startsWith('https://example.com')) {
return { action: 'allow' };
}
return { action: 'deny' };
});
// ✅ Content Security Policy
win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': ["default-src 'self'"]
}
});
});const { Notification } = require('electron');
// Check if supported
if (Notification.isSupported()) {
const notification = new Notification({
title: 'Notification Title',
body: 'Notification body text',
icon: path.join(__dirname, 'icon.png'),
silent: false,
urgency: 'normal', // 'normal', 'critical', 'low' (Linux only)
timeoutType: 'default', // 'default', 'never'
actions: [
{ type: 'button', text: 'Show' },
{ type: 'button', text: 'Dismiss' }
]
});
notification.show();
notification.on('click', () => {
console.log('Notification clicked');
});
notification.on('action', (event, index) => {
console.log('Action clicked:', index);
});
notification.on('close', () => {
console.log('Notification closed');
});
}// In renderer process
if ('Notification' in window) {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
new Notification('Hello!', {
body: 'This is a notification',
icon: 'icon.png'
});
}
});
}npm install electron-updaterMain Process:
const { app } = require('electron');
const { autoUpdater } = require('electron-updater');
const log = require('electron-log');
// Configure logging
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';
// Check for updates on startup
app.whenReady().then(() => {
autoUpdater.checkForUpdatesAndNotify();
});
// Auto-update events
autoUpdater.on('checking-for-update', () => {
console.log('Checking for update...');
});
autoUpdater.on('update-available', (info) => {
console.log('Update available:', info);
});
autoUpdater.on('update-not-available', (info) => {
console.log('Update not available:', info);
});
autoUpdater.on('error', (err) => {
console.log('Error in auto-updater:', err);
});
autoUpdater.on('download-progress', (progressObj) => {
let message = `Download speed: ${progressObj.bytesPerSecond}`;
message += ` - Downloaded ${progressObj.percent}%`;
message += ` (${progressObj.transferred}/${progressObj.total})`;
console.log(message);
});
autoUpdater.on('update-downloaded', (info) => {
console.log('Update downloaded:', info);
// Prompt user to restart
dialog.showMessageBox({
type: 'info',
title: 'Update Ready',
message: 'A new version has been downloaded. Restart to apply updates.',
buttons: ['Restart', 'Later']
}).then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall();
}
});
});
// Manual check
ipcMain.handle('check-for-updates', () => {
autoUpdater.checkForUpdates();
});package.json configuration:
{
"build": {
"appId": "com.example.app",
"productName": "MyApp",
"publish": [
{
"provider": "github",
"owner": "username",
"repo": "repo-name"
}
]
}
}const { app, screen, powerMonitor, powerSaveBlocker } = require('electron');
const os = require('os');
// App info
console.log(app.getName());
console.log(app.getVersion());
console.log(app.getLocale());
console.log(app.getPath('userData'));
// System info
console.log(process.platform); // 'darwin', 'win32', 'linux'
console.log(process.arch); // 'x64', 'arm64'
console.log(os.cpus());
console.log(os.totalmem());
console.log(os.freemem());
// Screen info
const primaryDisplay = screen.getPrimaryDisplay();
console.log(primaryDisplay.bounds);
console.log(primaryDisplay.workArea);
console.log(primaryDisplay.scaleFactor);
const allDisplays = screen.getAllDisplays();const { powerMonitor, powerSaveBlocker } = require('electron');
// Power events
app.whenReady().then(() => {
powerMonitor.on('suspend', () => {
console.log('System is going to sleep');
});
powerMonitor.on('resume', () => {
console.log('System is resuming');
});
powerMonitor.on('on-ac', () => {
console.log('System is on AC power');
});
powerMonitor.on('on-battery', () => {
console.log('System is on battery power');
});
// Check current state
console.log('Idle time:', powerMonitor.getSystemIdleTime());
console.log('Idle state:', powerMonitor.getSystemIdleState(60));
});
// Prevent sleep
const id = powerSaveBlocker.start('prevent-app-suspension');
console.log(powerSaveBlocker.isStarted(id));
powerSaveBlocker.stop(id);const { shell } = require('electron');
// Open external links
shell.openExternal('https://example.com');
// Open file/folder
shell.openPath('/path/to/file.txt');
shell.showItemInFolder('/path/to/file.txt');
// Move to trash
shell.trashItem('/path/to/file.txt');
// Beep
shell.beep();const { clipboard, nativeImage } = require('electron');
// Text
clipboard.writeText('Hello from Electron');
const text = clipboard.readText();
// HTML
clipboard.writeHTML('<b>Bold text</b>');
const html = clipboard.readHTML();
// Image
const image = nativeImage.createFromPath('/path/to/image.png');
clipboard.writeImage(image);
const clipImage = clipboard.readImage();
// Clear
clipboard.clear();const { globalShortcut } = require('electron');
app.whenReady().then(() => {
// Register shortcut
const ret = globalShortcut.register('CommandOrControl+X', () => {
console.log('CommandOrControl+X is pressed');
});
if (!ret) {
console.log('Registration failed');
}
// Check if registered
console.log(globalShortcut.isRegistered('CommandOrControl+X'));
});
app.on('will-quit', () => {
// Unregister shortcuts
globalShortcut.unregister('CommandOrControl+X');
globalShortcut.unregisterAll();
});// Open DevTools
mainWindow.webContents.openDevTools();
// Open in detached mode
mainWindow.webContents.openDevTools({ mode: 'detach' });
// Close DevTools
mainWindow.webContents.closeDevTools();
// Toggle DevTools
mainWindow.webContents.toggleDevTools();
// Check if open
mainWindow.webContents.isDevToolsOpened();VS Code launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": ["."],
"outputCapture": "std"
}
]
}// Console logging
console.log('Main process log');
console.error('Error in main process');
// Electron-log
const log = require('electron-log');
log.info('Info message');
log.warn('Warning message');
log.error('Error message');
// Custom log levels
log.transports.file.level = 'info';
log.transports.console.level = 'debug';
// Log file location
console.log(log.transports.file.getFile().path);// Main process errors
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Log error, show dialog, etc.
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
});
// Renderer process errors (in main)
mainWindow.webContents.on('crashed', (event, killed) => {
console.error('Renderer process crashed');
// Reload or restart
mainWindow.reload();
});
// GPU process crashed
app.on('gpu-process-crashed', (event, killed) => {
console.error('GPU process crashed');
});npm install --save-dev electron-builderpackage.json:
{
"name": "my-app",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder",
"build:win": "electron-builder --win",
"build:mac": "electron-builder --mac",
"build:linux": "electron-builder --linux"
},
"build": {
"appId": "com.example.myapp",
"productName": "My App",
"copyright": "Copyright © 2024",
"directories": {
"output": "dist"
},
"files": [
"main.js",
"preload.js",
"renderer.js",
"index.html",
"assets/**/*",
"node_modules/**/*"
],
"win": {
"target": ["nsis", "portable"],
"icon": "assets/icon.ico"
},
"mac": {
"target": ["dmg", "zip"],
"icon": "assets/icon.icns",
"category": "public.app-category.productivity"
},
"linux": {
"target": ["AppImage", "deb", "rpm"],
"icon": "assets/icon.png",
"category": "Utility"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
}
}{
"build": {
"mac": {
"identity": "Developer ID Application: Your Name (TEAM_ID)",
"hardenedRuntime": true,
"gatekeeperAssess": false,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist"
},
"afterSign": "scripts/notarize.js"
}
}{
"build": {
"win": {
"certificateFile": "path/to/certificate.pfx",
"certificatePassword": "password",
"signingHashAlgorithms": ["sha256"],
"signDlls": true
}
}
}// Load window content only when needed
const win = new BrowserWindow({ show: false });
win.once('ready-to-show', () => {
win.show();
});const win = new BrowserWindow({
webPreferences: {
offscreen: true
}
});
win.webContents.on('paint', (event, dirty, image) => {
// Handle rendered frame
});
win.webContents.startPainting();// Clear cache
session.defaultSession.clearCache();
// Limit cache size
app.commandLine.appendSwitch('disk-cache-size', '50000000'); // 50MB
// Disable GPU if not needed
app.disableHardwareAcceleration();
// Enable process reuse
app.commandLine.appendSwitch('disable-renderer-backgrounding');// Enable resource throttling
win.webContents.setBackgroundThrottling(true);
// Preload scripts
const preloadScript = `
// Preload critical resources
const link = document.createElement('link');
link.rel = 'preload';
link.href = 'styles.css';
link.as = 'style';
document.head.appendChild(link);
`;
// Use V8 snapshot
app.commandLine.appendSwitch('v8-cache-options', 'code');class WindowManager {
constructor() {
this.windows = new Map();
}
create(name, options) {
const win = new BrowserWindow(options);
this.windows.set(name, win);
win.on('closed', () => {
this.windows.delete(name);
});
return win;
}
get(name) {
return this.windows.get(name);
}
getAll() {
return Array.from(this.windows.values());
}
closeAll() {
this.windows.forEach(win => win.close());
}
}
const windowManager = new WindowManager();const Store = require('electron-store');
const store = new Store({
defaults: {
windowBounds: { width: 800, height: 600 },
theme: 'light'
}
});
// Save window state
function saveWindowState(win) {
store.set('windowBounds', win.getBounds());
}
// Restore window state
function createWindow() {
const bounds = store.get('windowBounds');
const win = new BrowserWindow(bounds);
win.on('close', () => saveWindowState(win));
return win;
}const { protocol } = require('electron');
app.whenReady().then(() => {
// Register custom protocol
protocol.registerFileProtocol('myapp', (request, callback) => {
const url = request.url.substr(8); // Remove 'myapp://'
callback({ path: path.normalize(`${__dirname}/${url}`) });
});
});
// Handle deep links
app.setAsDefaultProtocolClient('myapp');
app.on('open-url', (event, url) => {
event.preventDefault();
console.log('Opened URL:', url);
// Handle myapp:// URLs
});
// Windows/Linux
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Handle second instance
const url = commandLine.pop();
console.log('Deep link:', url);
});
}const { Worker } = require('worker_threads');
// Main process
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js');
worker.postMessage(data);
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
}
// worker.js
const { parentPort } = require('worker_threads');
parentPort.on('message', (data) => {
// Heavy computation
const result = processData(data);
parentPort.postMessage(result);
});const { session } = require('electron');
// Custom session
const customSession = session.fromPartition('persist:custom');
const win = new BrowserWindow({
webPreferences: {
session: customSession
}
});
// Cookies
customSession.cookies.get({ url: 'https://example.com' })
.then((cookies) => console.log(cookies));
customSession.cookies.set({
url: 'https://example.com',
name: 'session',
value: 'token123'
});
// Network interception
customSession.webRequest.onBeforeRequest((details, callback) => {
// Modify or cancel requests
if (details.url.includes('ads')) {
callback({ cancel: true });
} else {
callback({ cancel: false });
}
});
// Download handling
customSession.on('will-download', (event, item, webContents) => {
item.setSavePath('/path/to/save/file');
item.on('updated', (event, state) => {
if (state === 'interrupted') {
console.log('Download interrupted');
} else if (state === 'progressing') {
if (item.isPaused()) {
console.log('Download paused');
} else {
console.log(`Received: ${item.getReceivedBytes()}`);
}
}
});
item.once('done', (event, state) => {
if (state === 'completed') {
console.log('Download completed');
} else {
console.log(`Download failed: ${state}`);
}
});
});// Using native Node.js addons
const nativeModule = require('./build/Release/native-module');
// Better SQLite3
const Database = require('better-sqlite3');
const db = new Database('app.db');
// Native dependencies with electron-rebuild
// package.json
{
"scripts": {
"rebuild": "electron-rebuild -f -w better-sqlite3"
}
}1. Create React App:
npx create-react-app my-electron-app
cd my-electron-app
npm install --save-dev electron electron-is-dev concurrently wait-on cross-env2. Create main.js (Electron Main Process):
const { app, BrowserWindow } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
// Load from localhost in dev, or build folder in production
mainWindow.loadURL(
isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname, '../build/index.html')}`
);
if (isDev) {
mainWindow.webContents.openDevTools();
}
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});3. Create preload.js:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
sendMessage: (channel, data) => {
const validChannels = ['toMain'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receiveMessage: (channel, func) => {
const validChannels = ['fromMain'];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
invoke: (channel, data) => {
const validChannels = ['getData'];
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, data);
}
}
});4. Update package.json:
{
"name": "my-electron-app",
"version": "1.0.0",
"main": "public/main.js",
"homepage": "./",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron:dev": "concurrently \"cross-env BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron .\"",
"electron:build": "npm run build && electron-builder"
},
"build": {
"appId": "com.example.myapp",
"files": [
"build/**/*",
"node_modules/**/*",
"public/main.js",
"public/preload.js"
],
"directories": {
"buildResources": "assets"
}
}
}5. Move main.js and preload.js to public folder:
my-electron-app/
├── public/
│ ├── main.js
│ ├── preload.js
│ └── index.html
├── src/
│ ├── App.js
│ └── index.js
└── package.json
App.js:
import React, { useState, useEffect } from 'react';
import './App.css';
function App() {
const [message, setMessage] = useState('');
useEffect(() => {
// Listen for messages from main process
if (window.electron) {
window.electron.receiveMessage('fromMain', (data) => {
console.log('Received:', data);
setMessage(data);
});
}
}, []);
const sendToElectron = () => {
if (window.electron) {
window.electron.sendMessage('toMain', 'Hello from React!');
}
};
const fetchData = async () => {
if (window.electron) {
const result = await window.electron.invoke('getData', 'some-arg');
console.log('Result:', result);
}
};
return (
<div className="App">
<h1>Electron + React App</h1>
<button onClick={sendToElectron}>Send to Main</button>
<button onClick={fetchData}>Fetch Data</button>
<p>{message}</p>
</div>
);
}
export default App;useElectron.js:
import { useState, useEffect, useCallback } from 'react';
export const useElectron = () => {
const [isElectron] = useState(
window.electron !== undefined
);
const send = useCallback((channel, data) => {
if (isElectron) {
window.electron.sendMessage(channel, data);
}
}, [isElectron]);
const invoke = useCallback(async (channel, data) => {
if (isElectron) {
return await window.electron.invoke(channel, data);
}
}, [isElectron]);
return { isElectron, send, invoke };
};
export const useElectronListener = (channel, callback) => {
useEffect(() => {
if (window.electron) {
window.electron.receiveMessage(channel, callback);
}
return () => {
// Cleanup if needed
};
}, [channel, callback]);
};Usage:
import { useElectron, useElectronListener } from './hooks/useElectron';
function MyComponent() {
const { isElectron, send, invoke } = useElectron();
const [data, setData] = useState(null);
useElectronListener('fromMain', (message) => {
console.log('Received:', message);
});
const handleClick = async () => {
const result = await invoke('getData', 'param');
setData(result);
};
if (!isElectron) {
return <div>This app requires Electron</div>;
}
return (
<div>
<button onClick={handleClick}>Get Data</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}# Create new app with React template
npx create-electron-app my-app --template=webpack
# Or add to existing app
npm install --save-dev @electron-forge/plugin-webpackforge.config.js:
module.exports = {
packagerConfig: {},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {}
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin']
},
{
name: '@electron-forge/maker-deb',
config: {}
}
],
plugins: [
{
name: '@electron-forge/plugin-webpack',
config: {
mainConfig: './webpack.main.config.js',
renderer: {
config: './webpack.renderer.config.js',
entryPoints: [
{
html: './src/index.html',
js: './src/renderer.js',
name: 'main_window',
preload: {
js: './src/preload.js'
}
}
]
}
}
}
]
};npm create vite@latest my-electron-app -- --template react
cd my-electron-app
npm install
npm install --save-dev electron electron-builder vite-plugin-electronvite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import electron from 'vite-plugin-electron';
export default defineConfig({
plugins: [
react(),
electron([
{
entry: 'electron/main.js',
},
{
entry: 'electron/preload.js',
onstart(options) {
options.reload();
}
}
])
]
});package.json:
{
"scripts": {
"dev": "vite",
"build": "vite build && electron-builder",
"preview": "vite preview"
}
}Install dependencies:
npm install --save-dev @types/node @types/react @types/react-dom typescriptelectron.d.ts:
export interface IElectronAPI {
sendMessage: (channel: string, data: any) => void;
receiveMessage: (channel: string, func: (...args: any[]) => void) => void;
invoke: (channel: string, data?: any) => Promise<any>;
}
declare global {
interface Window {
electron: IElectronAPI;
}
}Usage in React component:
import React, { useState, useEffect } from 'react';
const App: React.FC = () => {
const [message, setMessage] = useState<string>('');
useEffect(() => {
if (window.electron) {
window.electron.receiveMessage('fromMain', (data: string) => {
setMessage(data);
});
}
}, []);
const handleInvoke = async (): Promise<void> => {
if (window.electron) {
const result = await window.electron.invoke('getData');
console.log(result);
}
};
return (
<div>
<h1>Electron + React + TypeScript</h1>
<button onClick={handleInvoke}>Invoke</button>
<p>{message}</p>
</div>
);
};
export default App;Nextron is the easiest way to integrate Next.js with Electron.
1. Create a new Nextron app:
npx create-nextron-app my-app
cd my-app
npm install2. Available templates:
# Basic Next.js + Electron
npx create-nextron-app my-app --example basic-lang-javascript
# With TypeScript
npx create-nextron-app my-app --example basic-lang-typescript
# With Tailwind CSS
npx create-nextron-app my-app --example with-tailwindcss
# With TypeScript + Tailwind
npx create-nextron-app my-app --example with-typescript-tailwindcss3. Project structure:
my-app/
├── main/
│ ├── background.js # Electron main process
│ └── preload.js # Preload script
├── renderer/
│ ├── pages/
│ │ ├── _app.js
│ │ ├── index.js
│ │ └── next.js
│ └── public/
├── resources/ # App icons
└── package.json
4. Run the app:
npm run dev # Development mode
npm run build # Build for production1. Create Next.js app:
npx create-next-app my-electron-nextjs-app
cd my-electron-nextjs-app
npm install --save-dev electron electron-builder electron-is-dev2. Create electron directory:
my-app/
├── electron/
│ ├── main.js
│ └── preload.js
├── pages/
├── public/
└── package.json
3. electron/main.js:
const { app, BrowserWindow } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
const url = isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname, '../out/index.html')}`;
mainWindow.loadURL(url);
if (isDev) {
mainWindow.webContents.openDevTools();
}
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});4. electron/preload.js:
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
platform: process.platform,
sendMessage: (channel, data) => {
ipcRenderer.send(channel, data);
},
receiveMessage: (channel, func) => {
ipcRenderer.on(channel, (event, ...args) => func(...args));
},
invoke: (channel, data) => {
return ipcRenderer.invoke(channel, data);
}
});5. Update package.json:
{
"name": "my-electron-nextjs-app",
"version": "1.0.0",
"main": "electron/main.js",
"scripts": {
"dev": "next dev",
"build": "next build",
"export": "next export",
"start": "next start",
"electron": "electron .",
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:3000 && electron .\"",
"electron:build": "npm run build && npm run export && electron-builder",
"dist": "npm run build && npm run export && electron-builder"
},
"build": {
"appId": "com.example.nextron",
"files": [
"electron/**/*",
"out/**/*"
],
"directories": {
"buildResources": "resources",
"output": "dist"
}
}
}6. next.config.js (for static export):
module.exports = {
output: 'export',
images: {
unoptimized: true,
},
// Optional: Change the output directory
distDir: 'out',
};Since Electron runs static files, API routes need special handling.
Option 1: Use IPC instead of API routes
electron/main.js:
const { ipcMain } = require('electron');
// Handle API calls via IPC
ipcMain.handle('api:users', async (event, userId) => {
// Your API logic here
const user = await fetchUserFromDatabase(userId);
return user;
});
ipcMain.handle('api:posts', async () => {
const posts = await fetchPosts();
return posts;
});pages/index.js:
import { useState, useEffect } from 'react';
export default function Home() {
const [user, setUser] = useState(null);
useEffect(() => {
async function fetchUser() {
if (window.electron) {
const userData = await window.electron.invoke('api:users', 1);
setUser(userData);
}
}
fetchUser();
}, []);
return (
<div>
<h1>Next.js + Electron</h1>
{user && <p>Welcome, {user.name}!</p>}
</div>
);
}Option 2: Run Next.js server in Electron
const { app, BrowserWindow } = require('electron');
const next = require('next');
const path = require('path');
const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev, dir: path.join(__dirname, '../') });
const handle = nextApp.getRequestHandler();
let mainWindow;
nextApp.prepare().then(() => {
// Start Next.js server
const express = require('express');
const server = express();
server.all('*', (req, res) => {
return handle(req, res);
});
const PORT = 3000;
server.listen(PORT, (err) => {
if (err) throw err;
console.log(`> Ready on http://localhost:${PORT}`);
createWindow();
});
});
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
mainWindow.loadURL('http://localhost:3000');
}pages/_app.js:
import { useEffect } from 'react';
import '../styles/globals.css';
function MyApp({ Component, pageProps }) {
useEffect(() => {
// Check if running in Electron
if (typeof window !== 'undefined' && window.electron) {
console.log('Running in Electron');
console.log('Platform:', window.electron.platform);
}
}, []);
return <Component {...pageProps} />;
}
export default MyApp;pages/index.js:
import { useState, useEffect } from 'react';
import Head from 'next/head';
import styles from '../styles/Home.module.css';
export default function Home() {
const [isElectron, setIsElectron] = useState(false);
const [message, setMessage] = useState('');
useEffect(() => {
setIsElectron(typeof window !== 'undefined' && window.electron);
if (window.electron) {
window.electron.receiveMessage('update', (msg) => {
setMessage(msg);
});
}
}, []);
const handleClick = async () => {
if (window.electron) {
const result = await window.electron.invoke('get-data', 'test');
alert(result);
}
};
return (
<div className={styles.container}>
<Head>
<title>Electron + Next.js App</title>
</Head>
<main className={styles.main}>
<h1>Electron + Next.js</h1>
{isElectron ? (
<>
<p>Running in Electron ✅</p>
<button onClick={handleClick}>Call Electron API</button>
{message && <p>Message: {message}</p>}
</>
) : (
<p>Running in Browser 🌐</p>
)}
</main>
</div>
);
}Install dependencies:
npm install --save-dev @types/node typescriptelectron/main.ts:
import { app, BrowserWindow } from 'electron';
import * as path from 'path';
import * as isDev from 'electron-is-dev';
let mainWindow: BrowserWindow | null;
function createWindow(): void {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
const url = isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname, '../out/index.html')}`;
mainWindow.loadURL(url);
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});types/electron.d.ts:
export interface IElectronAPI {
platform: string;
sendMessage: (channel: string, data: any) => void;
receiveMessage: (channel: string, func: (...args: any[]) => void) => void;
invoke: (channel: string, data?: any) => Promise<any>;
}
declare global {
interface Window {
electron: IElectronAPI;
}
}main/background.ts:
import { app, ipcMain } from 'electron';
import serve from 'electron-serve';
import { createWindow } from './helpers';
const isProd = process.env.NODE_ENV === 'production';
if (isProd) {
serve({ directory: 'app' });
} else {
app.setPath('userData', `${app.getPath('userData')} (development)`);
}
(async () => {
await app.whenReady();
const mainWindow = createWindow('main', {
width: 1000,
height: 600,
});
if (isProd) {
await mainWindow.loadURL('app://./home.html');
} else {
const port = process.argv[2];
await mainWindow.loadURL(`http://localhost:${port}/home`);
mainWindow.webContents.openDevTools();
}
})();
app.on('window-all-closed', () => {
app.quit();
});
// IPC Handlers
ipcMain.handle('get-app-version', () => {
return app.getVersion();
});
ipcMain.handle('get-user-data-path', () => {
return app.getPath('userData');
});renderer/pages/home.tsx:
import React from 'react';
import { useEffect, useState } from 'react';
import Head from 'next/head';
export default function HomePage() {
const [version, setVersion] = useState('');
useEffect(() => {
async function getVersion() {
if (window.electron) {
const ver = await window.electron.invoke('get-app-version');
setVersion(ver);
}
}
getVersion();
}, []);
return (
<>
<Head>
<title>Home - Nextron</title>
</Head>
<div>
<h1>Hello Nextron! ⚡️</h1>
<p>App Version: {version}</p>
</div>
</>
);
}- Static Export: Always use
output: 'export'in next.config.js for production builds - Image Optimization: Disable Next.js image optimization for Electron (
unoptimized: true) - API Routes: Use IPC instead of Next.js API routes in production
- Environment Variables: Be careful with env vars - use different configs for dev/prod
- Build Output: Export to
outdirectory and include it in electron-builder files - Development: Use concurrently to run Next.js dev server and Electron together
- Routing: Use Next.js router normally; it works with static export
- State Management: Redux, Zustand, or Context API work normally
- SSG: Use
getStaticPropsfor data fetching at build time
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Focus the existing window
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
app.whenReady().then(createWindow);
}let splashWindow;
let mainWindow;
function createSplashScreen() {
splashWindow = new BrowserWindow({
width: 400,
height: 300,
transparent: true,
frame: false,
alwaysOnTop: true
});
splashWindow.loadFile('splash.html');
splashWindow.center();
}
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
show: false
});
mainWindow.loadFile('index.html');
mainWindow.once('ready-to-show', () => {
setTimeout(() => {
splashWindow.close();
mainWindow.show();
}, 2000);
});
}
app.whenReady().then(() => {
createSplashScreen();
createMainWindow();
});// Main process
const win = new BrowserWindow({
frame: false,
titleBarStyle: 'hidden'
});
// Renderer HTML
<div id="titlebar">
<div id="drag-region"></div>
<div id="window-controls">
<button id="minimize">−</button>
<button id="maximize">□</button>
<button id="close">×</button>
</div>
</div>
// Renderer CSS
#titlebar {
-webkit-app-region: drag;
height: 32px;
}
#window-controls {
-webkit-app-region: no-drag;
}
// Renderer JS
document.getElementById('minimize').addEventListener('click', () => {
window.api.minimize();
});
document.getElementById('maximize').addEventListener('click', () => {
window.api.maximize();
});
document.getElementById('close').addEventListener('click', () => {
window.api.close();
});- Electron Forge: Complete toolkit for building Electron apps
- Electron Builder: Build and publish Electron apps
- Electron React Boilerplate: Electron + React + Redux + Webpack
- Electron Vue: Electron + Vue.js
- Nextron: Electron + Next.js integration
electron-store: Persistent data storageelectron-updater: Auto-update functionalityelectron-log: Logging libraryelectron-reload: Auto-reload during developmentelectron-context-menu: Easily create context menuselectron-window-state: Save and restore window state
- Spectron: End-to-end testing framework (deprecated)
- Playwright: Modern E2E testing for Electron
- Jest: Unit testing framework
app.on('ready', () => {});
app.on('window-all-closed', () => {});
app.on('activate', () => {});
app.on('before-quit', () => {});
app.on('will-quit', () => {});
app.on('quit', () => {});width, height, x, y, minWidth, minHeight, maxWidth, maxHeight
resizable, movable, minimizable, maximizable, closable
fullscreen, fullscreenable, kiosk, alwaysOnTop
frame, transparent, backgroundColor, show, modal// File operations
'file:open', 'file:save', 'file:read', 'file:write'
// Window operations
'window:minimize', 'window:maximize', 'window:close'
// App operations
'app:quit', 'app:relaunch', 'app:getVersion'
// Data operations
'data:get', 'data:set', 'data:delete'This cheat sheet covers ElectronJs from beginner to advanced levels. Practice building real applications to master these concepts!