Skip to content

macOS Build & Notarization

Flamingo uses electron-builder to package and sign macOS distributables (DMG and ZIP). This guide covers the full build pipeline including code signing and notarization for distribution outside the Mac App Store.

  • macOS — building for macOS must be done on macOS
  • Xcode — install from the Mac App Store, open once to accept the license
  • Apple Developer account — enrolled in the Apple Developer Program ($99/year)
  • Xcode Command Line Tools — install with xcode-select --install

electron-builder automatically finds the appropriate certificates from your system keychain.

CertificateTypeLocation
Developer ID ApplicationSign the app bundleKeychain (login)
Developer ID InstallerSign the installer DMGKeychain (login)
  1. Go to developer.apple.com/account → Certificates, IDs & Profiles
  2. Create a Developer ID Application certificate
  3. Create a Developer ID Installer certificate (if building DMG)
  4. Download and double-click both to install in Keychain

The base macOS build config lives in client/package.json:

{
"build": {
"mac": {
"target": ["dmg", "zip"],
"category": "public.app-category.utilities",
"icon": "assets/icon.icns"
}
}
}

electron-builder signs macOS builds automatically when valid Developer ID Application certificates are found. To pin a specific certificate identity:

{
"mac": {
"target": ["dmg", "zip"],
"category": "public.app-category.utilities",
"icon": "assets/icon.icns",
"identity": "Developer ID Application: Your Name (TEAMID)"
}
}

macOS requires the Hardened Runtime for notarization. Create client/build/entitlements.mac.plist:

<?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-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

Update package.json to reference the entitlements:

{
"mac": {
"target": ["dmg", "zip"],
"category": "public.app-category.utilities",
"icon": "assets/icon.icns",
"hardenedRuntime": true,
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist"
}
}

Notarization submits your app to Apple for security scanning.

Set environment variables:

Terminal window
export APPLE_ID="your@appleid.com"
export APPLE_ID_PASSWORD="@keychain:AC_PASSWORD"

Store the app-specific password in Keychain:

Terminal window
xcrun notarytool store-credentials "AC_PASSWORD"
--apple-id "your@appleid.com"
--team-id "TEAMID"
--password "app-specific-password"

Generate an API key at developer.apple.com/account → Certificates, IDs & Profiles → Keys.

Terminal window
export APPLE_API_KEY="path/to/AuthKey_XXXXX.p8"
export APPLE_API_KEY_ID="XXXXX"
export APPLE_API_ISSUER="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

electron-builder needs the @electron/notarize package:

Terminal window
npm install --save-dev @electron/notarize

Create client/build/notarize.js:

const { notarize } = require('@electron/notarize')
exports.default = async function notarizing(context) {
const { electronPlatformName, appOutDir } = context
if (electronPlatformName !== 'darwin') return
const appName = context.packager.appInfo.productFilename
return await notarize({
appBundleId: 'com.flamingo-client.desktop',
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
teamId: process.env.APPLE_TEAM_ID,
})
}

Register the hook in package.json:

{
"build": {
"afterSign": "build/notarize.js"
}
}
Terminal window
npm run electron:mac

Output appears in client/dist-electron/:

dist-electron/
├── Flamingo-0.1.0.dmg
├── Flamingo-0.1.0-mac.zip
└── mac/
└── Flamingo.app
ScriptCommandOutput
macOSnpm run electron:macDMG + ZIP
Windowsnpm run electron:winNSIS installer
Linuxnpm run electron:linuxAppImage + deb + rpm
Allnpm run electron:allWindows + Linux

After a successful build, verify the notarization status:

Terminal window
spctl --assess --verbose /path/to/Flamingo.app

Look for accepted in the output.

”No matching provisioning profiles found”

Section titled “”No matching provisioning profiles found””

Ensure a Developer ID Application certificate exists in your login keychain. This is not the same as a development certificate.

  • Check that the app is fully signed: codesign -dvvv /path/to/Flamingo.app
  • Verify the hardened runtime is enabled
  • Ensure the app version has increased since the last submission (Apple caches by version)

“The executable is not signed with a trusted timestamp”

Section titled ““The executable is not signed with a trusted timestamp””

Add the --timestamp option by ensuring electron-builder is configured to include timestamps (default in recent versions).

electron-builder cannot find the certificate

Section titled “electron-builder cannot find the certificate”

List available identities:

Terminal window
security find-identity -v -p basic

Pass the identity name explicitly in the mac.identity config field.

For GitHub Actions, add the certificate (base64-encoded) as a repository secret and import it before building:

- name: Import certificate
run: |
echo $CERTIFICATE_BASE64 | base64 --decode > certificate.p12
security create-keychain -p temp temp.keychain
security default-keychain -s temp.keychain
security unlock-keychain -p temp temp.keychain
security import certificate.p12 -k temp.keychain -P "$CERT_PASSWORD" -A
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k temp temp.keychain

See the electron-builder CI docs for detailed guidance.