Published on

How To Code Sign And Notarize An Electron App On macOS


Sometime in 2022, I began building Static, a desktop CMS for static site content. My personal site had bounced around between Ghost and multiple SSG (static site generator) frameworks, and it was while writing a blog post in my code editor that I hit my breaking point. I had just finished installing a spellcheck extension in my VS Code editor when I realized the absurdity of what I was doing. It was, quite frankly, the worst writing experience I ever had, and it killed my motivation to write.

So I got to building.

Static has been around for a while now. All of my blog posts for the last year or so have been written using Static. However, I never did a proper launch of Static as a product or a tool. This was both because I had built it for myself and because I was collecting feedback from early testers. That feedback led me down a path earlier this year of rewriting the app as a web app, but I've since reverted back to desktop because the experience is significantly better. Over the last couple of weeks, I've committed myself to launching–and selling–Static finally.

To do so, I needed a distributable file that any macOS user could open and run safely on their machine. I'd tried to distribute a simple build by zipping up the .app file I was generating, but no one was able to run the app because of Apple's security settings. Every person who tried it would get an error like the one below.

I needed to sign and notarize the app. Fortunately, I have an Apple Developer account that I pay for, so I figured this would be easy. Electron, and Electron Forge (which Static is built with) even have seemingly good documentation on how to do this. Unfortunately, it wasn't as easy as just following the instructions documented.

I had signing certificates on my machine already from previous apps I had built (one of which I distributed through the Apple AppStore), so I figured I could use those. I added the following configuration to my package.json file and crossed my fingers.

module.exports = { // ... packagerConfig: { // ... osxNotarize: { tool: 'notarytool', appleId: process.env.APPLE_ID, appleIdPassword: process.env.APPLE_PASSWORD, teamId: process.env.APPLE_TEAM_ID } } // ... };

Of course, it did not work. I kept getting errors with the notarytool's connection process to Apple's servers. It seemed my certificates could not be found. So, I ran the following command:

security find-identity -p codesigning -v

This returned 0 valid identities. That would explain why I couldn't sign and notarize the app, but I definitely had signing certificates on my machine. No matter, I decided I would just export new certs.

I followed this guide to generate new certificates, making sure I had an Apple Developer ID cert and an Apple Developer App cert as the Electron Forge docs suggested. I downloaded the certificate file to my machine, and looked for ways to load that specific file into my packager config.

Electron Forge had docs that seemed to fit my needs:

module.exports = { // ... packagerConfig: { // ... osxNotarize: { tool: 'notarytool', keychain: 'my-keychain', keychainProfile: 'my-keychain-profile' } } // ... };

I thought I needed to make sure the certificate was part of my machine's Apple Keychain profile. I found a guide that helped me load my certificate into my Keychain profile here. I did that, then reran the packaging script for the app.

It still failed.

By this point, I had been working on building and packaging my app off and on for two days. I was getting frustrated and I started wishing I had built the app in Swift so that I could have just used Xcode's built-in tools for signing and notarization.


I remembered seeing in the Electron Forge docs a note on Xcode. I figured the Xcode toolchain was used behind the scenes (which it is), and I knew I had Xcode installed and updated. So I breezed past this section, but there was one line I missed and it was critical.

I followed the link and found some underwhelming documentation from Apple on loading certificated through Xcode. I fired up Xcode and stumbled around until I found a screen for managing certificates. There, I found the problem. I had one expired certificate loaded in Xcode.

I added the Mac Installer Distribution certificate and the Apple Developer ID certificate. Now, when I went back to my terminal and ran security find-identity -p codesigning -v it showed one valid identity profile.

I was in business! When I ran my packaging script now, everything worked as expected and I had a signed dmg file I could distribute safely to anyone.

Maybe it's my inability to read docs closely enough the first or second or 20th time, or maybe this process is as confusing and painful as it seemed to be. Either way, I wanted to write this down so that future me could find it and use it as a guide. So, the tl;dr solution to code signing and notarizing an app built with Electron Forge is as follows:

  1. Make sure you have an Apple Developer account (there are other solutions, but I've never tried them)

  2. Sign into your Developer Account and make sure you have the Mac Installer Distribution cert and the Developer ID Application cert created. You may have other certs as well, but you'll need those two for macOS apps.

  3. Open Xcode and click on the Xcode menu bar option in the top-left.

  4. Choose the Settings option and you'll see a manage certificates button on the first page.

  5. Click the plus button and install both the Mac Installer Distribution cert and the Developer ID Application cert.

  6. Set up your packager configuration like so

module.exports = { // ... packagerConfig: { // ... osxNotarize: { tool: 'notarytool', appleId: process.env.APPLE_ID, appleIdPassword: process.env.APPLE_PASSWORD, teamId: process.env.APPLE_TEAM_ID } } // ... };

That should allow you to sign and notarize your app. Thankfully, I was able to figure this out, and Static is now out there in the world with both an Apple Silicon build and an Intel chip build. If you're interested in using a beautiful WYSIWYG editor to write blog posts to your local SSG site's blog folder, give Static a try.