Building Production Mac Apps with Electron & Angular: Complete Desktop Deployment Guide
Master Mac app distribution with Electron and Angular. Learn code signing, notarization, DMG creation, Mac App Store submission, and direct distribution workflows from real production experience.
Summarize with:

Introduction
Building a production-ready Mac desktop application using Electron and Angular requires navigating code signing, notarization, certificate management, and distribution workflows that are often undocumented or scattered across multiple sources. This comprehensive guide covers everything you need to know about deploying Angular desktop apps on macOS, from Electron setup to Mac App Store distribution. Whether you're converting a web app to desktop, building cross-platform applications, or publishing Mac apps, this production-tested approach will help you master Electron Mac deployment, code signing workflows, and notarization processes.
TL;DR: Electron enables rapid desktop app development with web technologies, but Mac distribution requires mastering code signing, notarization, and DMG creation. The complexity lies in Apple's security requirements and certificate management—not in Electron itself.
Related Reading: Explore our Mobile Deployment Guide for complementary insights.
Why Electron for Mac Desktop Apps
| Approach | Development Speed | Native Features | Bundle Size | Maintenance |
|---|---|---|---|---|
| Electron | Very Fast | Full Access | Larger (~100MB+) | Single Codebase |
| Native (Swift) | Slow | Full Access | Smaller (~20MB) | Separate Codebase |
| Electron (Optimized) | Fast | Full Access | Medium (~60MB) | Single Codebase |
Electron's Advantages for Mac Apps:
- Web Technology Stack – Leverage existing Angular/React/Vue skills for desktop app development
- Cross-Platform Desktop Apps – Same codebase works on Mac, Windows, and Linux platforms
- Native macOS APIs – Full access to macOS system features and native desktop functionality
- Rapid Desktop Development – Faster iteration than native Swift development for Mac applications
- Rich npm Ecosystem – Extensive package ecosystem available for desktop app features and integrations
Architecture Overview
Production Electron Mac applications use this architecture pattern:
Key Components:
- Main Process (
electron/main.ts) – Controls app lifecycle and native APIs - Renderer Process – Angular application running in BrowserWindow
- IPC Bridge – Secure communication between main and renderer
- Electron Forge – Build and packaging system
- Notarization Service – Apple's security verification
Tech Stack & Configuration
Core Framework Stack
Desktop Framework:
- Electron 38.1.0 – Latest stable desktop app framework with enhanced security for Mac applications
- Electron Forge 7.9.0 – Modern build and packaging system for streamlined Mac app distribution
- Angular 17.3.12 – Web application framework optimized for desktop app development
- TypeScript 5.4 – Type safety across Electron and Angular for reliable desktop application development
Build Tools:
- Electron Forge Makers – DMG, ZIP, NSIS installers for cross-platform desktop app distribution
- @electron/notarize – Apple notarization integration for Mac App Store compliance
- appdmg – DMG creation with custom layouts for professional Mac app installers
Electron Forge Configuration
⚙️ Click to view Electron Forge Production Configuration
// forge.config.js - Complete Production Configuration
const { FusesPlugin } = require('@electron-forge/plugin-fuses');
const { FuseV1Options, FuseVersion } = require('@electron/fuses');
const packageJson = require('./package.json');
module.exports = {
packagerConfig: {
asar: true,
// Code signing configuration for macOS
osxSign: process.env.MAS_BUILD
? {
// App Store configuration
identity: 'Apple Distribution: Your Company (TEAM_ID)',
'hardened-runtime': true,
'gatekeeper-assess': false,
entitlements: 'build/mac/entitlements.mas.plist',
'entitlements-inherit': 'build/mac/entitlements.mas.inherit.plist',
'signature-flags': 'library',
'pre-embed-provisioning-profile': 'build/mac/YourApp_Mac_App_Store.provisionprofile',
}
: {
// Direct distribution configuration
identity:
process.env.APPLE_SIGNING_IDENTITY ||
'Developer ID Application: Your Company (TEAM_ID)',
'hardened-runtime': true,
'gatekeeper-assess': false,
entitlements: 'build/mac/entitlements.mac.plist',
'entitlements-inherit': 'build/mac/entitlements.mac.plist',
'signature-flags': 'library',
'pre-embed-provisioning-profile': false,
},
appBundleId: 'com.yourcompany.yourapp',
appCategoryType: 'public.app-category.productivity',
icon: 'src/assets/media/logos/icons/mac/icon.icns',
name: 'YourApp',
executableName: 'YourApp',
extendInfo: {
CFBundleIdentifier: 'com.yourcompany.yourapp',
CFBundleName: 'YourApp',
CFBundleDisplayName: 'YourApp',
CFBundleExecutable: 'YourApp',
CFBundleShortVersionString: packageJson.version,
CFBundleVersion: '1',
CFBundleDevelopmentRegion: 'en',
CFBundlePackageType: 'APPL',
CFBundleInfoDictionaryVersion: '6.0',
},
},
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives',
config: {},
},
new FusesPlugin({
version: FuseVersion.V1,
[FuseV1Options.RunAsNode]: false,
[FuseV1Options.EnableCookieEncryption]: true,
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
[FuseV1Options.EnableNodeCliInspectArguments]: false,
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
[FuseV1Options.OnlyLoadAppFromAsar]: true,
}),
],
};
Configuration Highlights:
- Conditional Signing – MAS_BUILD env var switches between App Store and direct distribution
- Hardened Runtime – Required for notarization
- Security Fuses – Disable Node.js injection and enable integrity validation
- ASAR Packaging – Archive format for app resources
Electron Main Process
Main Process Implementation
⚡ Click to view Electron Main Process Implementation
// electron/main.ts - Production Main Process
import { app, BrowserWindow, dialog, ipcMain, safeStorage } from 'electron';
import * as path from 'path';
import * as url from 'url';
import { createMenu } from './menu';
let mainWindow: BrowserWindow | null;
function createWindow() {
const windowOptions: any = {
width: 1200,
height: 800,
title: 'YourApp',
backgroundColor: '#FFFFFF',
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
webSecurity: true,
devTools: process.env.NODE_ENV === 'development',
preload: path.join(__dirname, 'preload.js'),
experimentalFeatures: true,
},
};
mainWindow = new BrowserWindow(windowOptions);
// Set custom headers for platform identification
mainWindow.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
const platform = 'mac';
const appOrigin =
process.env.NODE_ENV === 'development'
? `https://yourdomain.com/dev-${platform}`
: `https://yourdomain.com/${platform}`;
details.requestHeaders['x-app-origin'] = appOrigin;
details.requestHeaders['x-app-platform'] = platform;
details.requestHeaders['x-app-version'] = app.getVersion();
callback({ requestHeaders: details.requestHeaders });
});
// Handle permission requests
mainWindow.webContents.session.setPermissionRequestHandler(
(webContents, permission, callback) => {
// Allow microphone, camera, notifications, file system
if (['media', 'display-capture', 'notifications', 'fileSystem'].includes(permission)) {
callback(true);
return;
}
callback(false);
},
);
// Load application
if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:8000');
mainWindow.webContents.openDevTools();
} else {
const indexPath = path.join(app.getAppPath(), 'dist/yourapp/index.html');
mainWindow.loadURL(
url.format({
pathname: indexPath,
protocol: 'file:',
slashes: true,
}),
);
}
mainWindow.on('closed', () => {
mainWindow = null;
});
}
app.setName('YourApp');
app.setAppUserModelId('com.yourcompany.yourapp');
app.whenReady().then(() => {
createWindow();
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
Security Best Practices:
- Context Isolation – Prevents renderer from accessing Node.js
- Node Integration Disabled – Renderer runs in secure sandbox
- Web Security Enabled – Enforces CORS and content security
- Preload Script – Controlled bridge between main and renderer
Code Signing Setup
Certificate Types
| Certificate Type | Use Case | Identity Format |
|---|---|---|
| Developer ID Application | Direct Distribution | Developer ID Application: Your Company |
| Apple Distribution | Mac App Store | Apple Distribution: Your Company |
| Apple Development | Development/Testing | Apple Development: Your Name |
Certificate Installation
# List available signing identities
security find-identity -v -p codesigning
# Export certificate for backup
security export -k ~/Library/Keychains/login.keychain-db \
-t identities -f pkcs12 -o ./certificate.p12 \
"Developer ID Application: Your Company (TEAM_ID)"
Certificate Requirements:
- Valid Apple Developer Program membership ($99/year)
- Certificates downloaded from Apple Developer Portal
- Private keys stored securely in Keychain
- Team ID matching provisioning profiles
Notarization Workflow
Notarization Script
🔐 Click to view Notarization Script Implementation
// electron/notarize.js - Production Notarization
require('dotenv').config();
const { notarize } = require('@electron/notarize');
exports.default = async function notarizing(context) {
console.log(`🔐 Starting notarization process...`);
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
console.log(`ℹ️ Notarization not required for platform: ${electronPlatformName}`);
return;
}
const appName = context.packager.appInfo.productFilename;
const appPath = `${appOutDir}/${appName}.app`;
// Verify environment variables
if (
!process.env.APPLE_TEAM_ID ||
!process.env.APPLE_ID ||
!process.env.APPLE_APP_SPECIFIC_PASSWORD
) {
throw new Error('Missing required Apple notarization environment variables.');
}
const data = {
tool: 'notarytool', // Use modern notarytool (not deprecated altool)
teamId: process.env.APPLE_TEAM_ID,
appBundleId: 'com.yourcompany.yourapp',
appPath: appPath,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD,
};
console.log('⏳ Submitting to Apple for notarization...');
console.log(' This may take 5-15 minutes...');
await notarize(data);
console.log('✅ Notarization complete!');
console.log('🎉 Your app is now ready for distribution!');
};
Notarization Requirements:
- Hardened Runtime – Enabled in entitlements
- Code Signing – App and all nested code signed
- Valid Certificates – Developer ID or Apple Distribution
- App-Specific Password – Generated in Apple ID account settings
Environment Variables:
# .env file (never commit)
APPLE_TEAM_ID=YOUR_TEAM_ID
APPLE_ID=your-email@example.com
APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx
DMG Creation Process
DMG Creation Script
📦 Click to view DMG Creation Script
// scripts/create-dmg.js - Custom DMG Builder
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
console.log('🎯 Creating DMG file...');
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
const version = packageJson.version;
// Verify app bundle exists
const appPath = 'out/YourApp-darwin-arm64/YourApp.app';
if (!fs.existsSync(appPath)) {
console.error('❌ App bundle not found. Please run electron:package first.');
process.exit(1);
}
// Remove existing DMG
const dmgPath = 'out/YourApp.dmg';
if (fs.existsSync(dmgPath)) {
console.log('🗑️ Removing existing DMG...');
fs.unlinkSync(dmgPath);
}
// Create DMG configuration
const dmgConfig = {
title: 'YourApp',
icon: path.resolve('src/assets/media/logos/icons/mac/icon.icns'),
background: path.resolve('src/assets/media/logos/icons/png/256x256.png'),
contents: [
{ x: 410, y: 150, type: 'link', path: '/Applications' },
{ x: 130, y: 150, type: 'file', path: path.resolve(appPath) },
],
window: {
width: 540,
height: 380,
},
};
// Write config and create DMG
const configPath = 'out/dmg-config.json';
fs.writeFileSync(configPath, JSON.stringify(dmgConfig, null, 2));
try {
console.log('📦 Building DMG...');
execSync(`cd out/YourApp-darwin-arm64 && npx appdmg ../dmg-config.json ../YourApp.dmg`, {
stdio: 'inherit',
});
// Sign the DMG
console.log('🔐 Signing DMG...');
const signingIdentity =
process.env.APPLE_SIGNING_IDENTITY ||
'Developer ID Application: Your Company (TEAM_ID)';
execSync(`codesign --sign "${signingIdentity}" --timestamp --options runtime out/YourApp.dmg`, {
stdio: 'inherit',
});
// Verify signature
console.log('🔍 Verifying DMG signature...');
execSync('codesign --verify --verbose out/YourApp.dmg', {
stdio: 'inherit',
});
console.log('✅ DMG created and signed successfully: out/YourApp.dmg');
} catch (error) {
console.error('❌ Failed to create/sign DMG:', error.message);
process.exit(1);
}
DMG Features:
- Custom Layout – App icon and Applications folder link
- Background Image – Branded installer experience
- Code Signing – DMG itself is signed for security
- Stapling – Notarization ticket attached to DMG
Build Pipeline
Build Workflow:
- Clean – Remove previous builds
- Version Update – Increment version numbers
- Angular Build – Production-optimized web assets with base-href for file:// protocol
- TypeScript Compile – Electron main process compilation
- Package – Create app bundle using Electron Forge
- Make – Generate installers (DMG, ZIP, etc.)
- Notarize – Apple security verification (automated)
- DMG Creation – Custom installer creation with branding
Distribution Methods
Direct DMG Distribution
Advantages:
- ✅ Faster distribution (no app store review)
- ✅ Full control over pricing and updates
- ✅ Can distribute outside Mac App Store
- ✅ No revenue sharing (30% fee)
Requirements:
- ✅ Developer ID certificate
- ✅ Notarization (required for macOS 10.15+)
- ✅ Signed DMG file
Mac App Store Distribution
Advantages:
- ✅ Discoverability through App Store
- ✅ Automatic updates via App Store
- ✅ User trust and security verification
- ✅ Sandboxed execution environment
Requirements:
- ✅ Apple Distribution certificate
- ✅ Provisioning profile
- ✅ App Store Connect account
- ✅ App Store review approval
Security & Entitlements
Entitlements Configuration
🔑 Click to view Mac Entitlements Configuration
<!-- build/mac/entitlements.mac.plist - Direct Distribution -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
Common Entitlements:
- JIT Compilation – Required for V8 JavaScript engine
- Audio Input – Microphone access
- Camera – Webcam access
- Network Client – Internet connectivity
- File Access – User-selected file read/write
Hardened Runtime
Required for Notarization:
- Prevents code injection
- Restricts library loading
- Enforces signature validation
- Controls system resource access
Common Challenges & Solutions
Challenge 1: Notarization Failures
Problem: App rejected during notarization
Common Causes:
- Missing hardened runtime
- Unsigned nested code
- Invalid entitlements
- Expired certificates
Solution:
# Verify app signature
codesign -dv --verbose=4 YourApp.app
# Check for unsigned libraries
find YourApp.app -type f -exec codesign -v {} \;
# Review notarization logs
xcrun notarytool history --apple-id "your-email" --password "app-specific-password" --team-id "TEAM_ID"
Challenge 2: DMG Not Opening
Problem: "YourApp.dmg is damaged" error
Solution:
# Remove quarantine attribute
xattr -cr YourApp.dmg
# Verify signature
codesign --verify --verbose YourApp.dmg
# Re-sign if needed
codesign --sign "Developer ID Application: Your Company" --timestamp YourApp.dmg
Challenge 3: Code Signing Errors
Problem: Certificate chain issues
Solution:
# Install WWDR intermediate certificate
curl -O https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
sudo security add-trusted-cert -d -r trustRoot -k "/Library/Keychains/System.keychain" AppleWWDRCAG3.cer
# Verify certificate chain
security verify-cert -c certificate.p12
Challenge 4: Large Bundle Size
Problem: App bundle too large (>150MB), affecting download times and user experience
Solution:
- Enable ASAR packaging for compressed app archives (already configured in Electron Forge)
- Exclude unnecessary files in forge.config.js using ignore patterns
- Tree-shake unused dependencies during Angular build process
- Optimize images and assets using WebP format and appropriate compression
- Use Electron's built-in optimization flags and code splitting for desktop applications
- Consider lazy-loading modules to reduce initial bundle size for Mac apps
Performance Optimization
Bundle Size Optimization
📉 Click to view Bundle Size Optimization Configuration
// forge.config.js - File Exclusion
ignore: [
/^\/src\/(?!assets\/media\/logos\/icons\/(mac|win|png)\/)/,
/^\/\.angular/,
/^\/\.git/,
/^\/ios/,
/^\/android/,
/^\/docs/,
/^\/build/,
/^\/release/,
/^\/out/,
/^\/logs/,
]
Optimization Results:
| Optimization | Before | After | Savings |
|---|---|---|---|
| ASAR Packaging | 120MB | 95MB | 21% |
| File Exclusion | 95MB | 78MB | 18% |
| Image Optimization | 78MB | 65MB | 17% |
| Total | 120MB | 65MB | 46% |
Deployment Metrics
Build Performance
| Metric | Time |
|---|---|
| Angular Build | 45s |
| Electron Package | 30s |
| Code Signing | 15s |
| Notarization | 8-15 min |
| DMG Creation | 10s |
| Total | ~20 min |
Distribution Statistics
- DMG Size: 65MB (compressed from 120MB)
- Installation Time: Less than 30 seconds
- User Satisfaction: High ratings typical for well-built Electron apps
- Crash Rate: Less than 0.1%
Real-World Insights
What Worked Well
- Electron Forge – Modern, well-maintained build system
- Automated Notarization – Integrated into build pipeline
- Custom DMG Layout – Professional installer experience
- TypeScript – Type safety across Electron and Angular
- Security Fuses – Enhanced app security
Lessons Learned
- Start with Certificates Early – Can take days to obtain
- Test Notarization Locally – Don't wait until production
- Automate Everything – Manual steps lead to errors
- Monitor Notarization Status – Check logs if failures occur
- Keep Certificates Secure – Back up but don't commit to git
Common Mistakes to Avoid
- ❌ Committing certificates or passwords to git
- ❌ Skipping notarization (required for macOS 10.15+)
- ❌ Not testing on fresh macOS installations
- ❌ Using deprecated altool instead of notarytool
- ❌ Missing hardened runtime entitlements
Conclusion
Building production Mac applications with Electron and Angular is entirely feasible and offers significant advantages in development speed and code reuse. The complexity lies in mastering Apple's security requirements—code signing, notarization, and certificate management—not in the Electron framework itself.
Key Takeaways
- Electron enables rapid desktop development with web technologies, making Mac app development accessible to web developers
- Code signing and notarization are required for modern macOS and essential for Mac App Store distribution
- Automation is critical – Manual processes lead to errors in desktop app deployment workflows
- Certificate management requires careful planning and secure storage for Mac app distribution
- Security fuses enhance app protection against tampering in Electron desktop applications
- DMG distribution provides flexible Mac app deployment options outside the App Store
Next Steps
- Set up Electron development environment
- Obtain Apple Developer certificates
- Configure build pipeline with automation
- Test notarization process thoroughly
- Create distribution workflows for both DMG and App Store
Further Reading
- Startup Growth Playbook: Complete Guide to Customer Acquisition, Retention & Monetization
- MCP (Model Context Protocol): Complete Guide to the 'USB-C' of AI Apps
- Advantages of Context Engineering Over Prompt Engineering: Complete 2025 Guide & Best Practices
- From Angular Web App to iOS & Android: Complete Mobile Deployment Guide with Capacitor
Frequently Asked Questions
Tags
Related Articles
Try Our Free Tools
AI Video Prompt Generator
Generate production-ready AI video prompts through conversation. Optimized for Sora 2 and Gemini video generation
AI Video Analyzer
Analyze video content frame-by-frame with AI. Content moderation, security monitoring, accessibility, and product demos
Text Language Detector & Translator
Detect any language and translate text instantly with browser-based AI