Native Integration
This is where Electron shines. Your app isn't trapped in a browser sandbox—it can read files, show system notifications, live in the tray, and respond to global shortcuts.
Native integration is the reason to choose Electron over a web app. A website can't watch your file system, can't show notifications when minimized, can't register global keyboard shortcuts. Desktop apps can—and users expect them to.
File System: Your App's Superpower
Web apps beg users to upload files through clunky dialogs. Electron apps can watch directories, read configuration files, and auto-save work—just like native apps. The key is doing this securely, through IPC handlers in the main process.
🎯 VibeBlaster Example
VibeBlaster watches a "screenshots" folder. When I drag a screenshot into the folder, the app automatically detects it, uploads it to storage, and makes it available for social media posts. No manual upload step—just drag and forget.
This "magic" is just a file watcher in the main process, sending IPC events to the renderer.
The Three File Patterns
📂File Dialogs
Open/save dialogs feel native because they are native. Electron uses the OS file picker, not a web recreation.
Use: User-initiated file operations
👁️File Watching
Monitor directories for changes. React to new files, modifications, or deletions in real-time.
Use: Auto-sync, hot reload, backup
💾Direct Read/Write
Read config files, write logs, save state. No user interaction needed—just specify the path.
Use: Settings, cache, app data
The Secure Pattern
File operations happen in the main process. The renderer requests operations via IPC, never touching the file system directly. This prevents malicious scripts from accessing arbitrary files.
// Main process: Handle file operations securely
ipcMain.handle('file:open', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Documents', extensions: ['txt', 'md', 'json'] }]
});
if (result.canceled) return null;
// Read file content and return to renderer
const content = await fs.readFile(result.filePaths[0], 'utf-8');
return { path: result.filePaths[0], content };
});
// Renderer: Request file operation through secure bridge
const file = await window.electronAPI.openFile();
if (file) {
setDocument(file.content);
}⚠️ Security: Validate All Paths
Never trust file paths from the renderer. Use path.normalize() and verify paths don't escape allowed directories. A malicious script could try ../../../etc/passwd—your IPC handler must reject this.
System Tray: Always Available
The system tray is prime real estate. Your app can live there even when the main window is closed, showing status, providing quick actions, and staying accessible without cluttering the taskbar.
Think of apps like Dropbox, Slack, or Discord—they're "always on" because they live in the tray. Users expect this pattern for background-capable apps.
When to Use Tray
- • Background sync or monitoring apps
- • Apps that need to stay running (chat, music)
- • Quick-access utilities (clipboard managers)
- • Status indicators (VPN, backup progress)
Tray Capabilities
- • Custom icon (can change dynamically)
- • Context menu on right-click
- • Tooltip on hover
- • Click to show/hide main window
- • Balloon notifications (Windows)
Implementation Pattern
Create the tray when the app starts, update it as state changes. On macOS, use template images (grayscale with "Template" suffix) for automatic dark mode support.
// Create tray with context menu
const tray = new Tray(iconPath);
tray.setToolTip('My App - Running');
const contextMenu = Menu.buildFromTemplate([
{ label: 'Show App', click: () => mainWindow.show() },
{ type: 'separator' },
{ label: 'Settings', click: () => openSettings() },
{ label: 'Quit', click: () => app.quit() }
]);
tray.setContextMenu(contextMenu);
// Click to toggle window visibility
tray.on('click', () => {
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
});💡 Dynamic Status Updates
Change the tray icon to reflect app state: green for connected, yellow for syncing, red for errors. Update the tooltip with current status. Rebuild the context menu when options change. Users learn to glance at your tray icon for instant status.
Native Notifications
Web notifications are limited and often blocked. Electron notifications use the OS notification system directly—they appear in Notification Center on macOS, Action Center on Windows, and respect user preferences like Do Not Disturb.
🎯 VibeBlaster Example
When a scheduled post goes live, VibeBlaster shows a notification even if the app is minimized. Clicking the notification opens the app and navigates to the post. This keeps users informed without requiring them to watch the app constantly.
Basic Notification
new Notification({
title: 'Task Complete',
body: 'Your export finished',
icon: iconPath
}).show();With Click Handler
const notif = new Notification({
title: 'New Message',
body: 'Click to view'
});
notif.on('click', () => {
mainWindow.show();
mainWindow.focus();
});⚠️ Platform Differences
Windows supports action buttons in notifications; macOS doesn't. macOS notifications persist in Notification Center; Windows notifications can be transient. Always test on each platform and check Notification.isSupported() before showing.
Global Shortcuts
Global shortcuts work even when your app isn't focused. This is essential for utilities like screenshot tools, clipboard managers, or quick-capture apps. The user presses a hotkey anywhere, and your app responds.
Registering Global Shortcuts
import { globalShortcut } from 'electron';
app.whenReady().then(() => {
// Register global shortcut
globalShortcut.register('CommandOrControl+Shift+Space', () => {
// Show/focus your app from anywhere
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
mainWindow.focus();
}
});
});
// IMPORTANT: Unregister when app quits
app.on('will-quit', () => {
globalShortcut.unregisterAll();
});✓ Good Shortcut Choices
- • Use modifier combinations (Ctrl+Shift+X)
- • Let users customize shortcuts
- • Check for conflicts before registering
✗ Avoid
- • Common OS shortcuts (Cmd+C, Cmd+V)
- • Single keys or simple combos
- • Shortcuts that conflict with popular apps
Platform-Specific Features
Each OS has unique features your app can leverage. The best Electron apps feel native on each platform by using these platform-specific APIs thoughtfully.
🍎 macOS
- • Touch Bar - Custom controls
- • Dock menu - Right-click actions
- • Dock badge - Notification count
- • Recent documents - File menu
- • Handoff - Continuity
🪟 Windows
- • Jump List - Taskbar shortcuts
- • Taskbar progress - Progress bar
- • Overlay icon - Status badge
- • Thumbnail toolbar - Preview buttons
- • Toast actions - Notification buttons
🐧 Linux
- • Desktop files - App launcher
- • Unity launcher - Quick list
- • AppIndicator - System tray
- • MPRIS - Media controls
- • XDG - Standard paths
💡 Pro Tip: Progressive Enhancement
Check process.platform and add platform-specific features as enhancements, not requirements. Your app should work everywhere, but feel extra polished on each platform. VibeBlaster adds Touch Bar controls on macOS and Jump List items on Windows—neither breaks the other platform.
Native Integration Best Practices
Your App Now Has Superpowers
You can read files, watch directories, show notifications, live in the system tray, and respond to global shortcuts. These are the features that make desktop apps feel powerful—and they're all accessible through simple Electron APIs.
Next up: Security. With great power comes great responsibility—we need to make sure these native capabilities can't be exploited by malicious code.