All posts
How to Sign and Notarize an Electron App on macOS (2026 Guide)

How to Sign and Notarize an Electron App on macOS (2026 Guide)

A complete step-by-step guide to signing and notarizing an Electron app for macOS using Electron Builder, Apple Developer ID, and notarytool. Covers certificates, API keys, entitlements, troubleshooting, and common signing errors.

By · · 15 min read

Looking for the story behind this guide?

This article focuses on the technical steps. If you're curious about the mistakes I made, the debugging rabbit holes, and the timestamp issue that nearly drove me insane, check out Signing and Notarizing an Electron App for macOS: Everything I Learned.

If you've built an Electron app and you're ready to distribute it on macOS, you've probably discovered that things are not nearly as simple as they are on Windows.

On Windows, most users can download your installer, click through a SmartScreen warning the first time, and they're done. On macOS, unsigned apps are treated much more aggressively. Users have to right click the application, choose Open, confirm another dialog, and sometimes even visit System Settings before they can launch it.

It works, but it's a terrible first impression.

I recently went through the entire process of signing and notarizing several Electron apps including PinBoard and ArcDLP. It took far longer than I expected, mostly because Apple's documentation assumes you already understand how all of their certificates, API keys, keychains, and developer tools fit together.

This guide explains every step I took from a completely working Electron project to a signed, notarized application that users can simply drag into Applications and launch like any other Mac app.

There are no screenshots because, honestly, I don't think they're necessary. Every button, menu, and page is described explicitly so you should be able to follow along even if Apple's UI changes slightly in the future.

What This Guide Assumes

This guide is not about building an Electron application. It assumes you already have one.

Specifically, I'm assuming:

  • You already have an Electron project.
  • You're using Electron Builder.
  • You already have a working package.json.
  • You can already build your application.
  • You have a Mac available for building macOS releases.

If you can already run something like npm run build:mac and it produces a .app or .dmg, you're in exactly the right place.

Before We Start

There are two completely different concepts that people often mix together.

Code Signing

Code signing proves who created your application.

When you sign your app, Apple embeds a cryptographic signature inside the application bundle. Whenever someone opens the app, macOS checks that signature and verifies that nobody has modified the application since you built it.

Think of it as putting a tamper-proof seal on your software.

Notarization

Notarization is something completely different.

After your app has been signed, Electron Builder uploads it to Apple. Apple scans the application for known malware and checks that everything looks legitimate.

If everything passes, Apple issues a notarization ticket. Electron Builder then staples that ticket to your application.

Signing proves who built the application.

Notarization proves Apple has checked it.

You need both.

What You'll Need

  • An active Apple Developer Program membership.
  • A Mac.
  • Xcode installed.
  • Your Electron project.

You do not need to publish your app on the Mac App Store.

This guide is specifically for distributing standalone applications directly from your own website.

Step 1: Create a Developer ID Application Certificate

The first thing we need is a signing certificate.

Open your browser and go to the Apple Developer website.

Sign in with the Apple ID that's enrolled in the Apple Developer Program.

Once you're logged in, navigate to:

Certificates, Identifiers & Profiles
↓
Certificates

Click the + button to create a new certificate.

Apple will present you with a long list of certificate types.

Choose:

Developer ID Application

This is the certificate used for distributing desktop applications outside the Mac App Store.

Don't choose Apple Development.

Don't choose Mac Development.

Don't choose Developer ID Installer.

The one you want is Developer ID Application.

Click Continue.

Apple will now ask you for something called a Certificate Signing Request.

At this point you probably won't have one yet.

That's perfectly normal.

Step 2: Create a Certificate Signing Request (CSR)

Open the Keychain Access application on your Mac.

You can find it using Spotlight or inside:

/Applications/Utilities

In the menu bar, click:

Keychain Access
↓
Certificate Assistant
↓
Request a Certificate From a Certificate Authority...

A small dialog appears asking for three pieces of information.

Fill it in like this:

  • Your Apple Developer email address.
  • Your name.
  • Select "Saved to disk".

Leave the CA Email Address field empty.

Save the file somewhere convenient such as your Downloads folder.

Keychain Access has now generated two things.

The file you just saved is the public half of the request.

At the same time, macOS quietly created a matching private key and stored it securely inside your login keychain.

This private key never leaves your Mac.

That's important because your signing certificate is useless without it.

Step 3: Upload The CSR

Go back to the Apple Developer website.

Upload the Certificate Signing Request you just created.

Apple will generate a Developer ID Application certificate.

Download the resulting .cer file.

You're only one click away from having a working signing identity.

Step 4: Install The Certificate

The file you downloaded from Apple is only half of the puzzle.

Double click the .cer file.

Keychain Access will open automatically and import the certificate into your login keychain.

Because the private key was generated earlier when you created the Certificate Signing Request, macOS automatically pairs the two together.

If everything went correctly, you should now have a complete signing identity.

This is the certificate Electron Builder will use every time it signs your application.

Step 5: Verify Everything Worked

Before touching Electron Builder, let's make sure macOS can actually see the certificate.

Open Terminal and run:

security find-identity -v -p codesigning

You should see something similar to:

1) XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX "Developer ID Application: Your Name (TEAMID)"

If you see your Developer ID Application certificate, congratulations. Your Mac is now capable of signing applications.

At this point a lot of guides stop.

Unfortunately, we're only about halfway there.

Step 6: Create An App Store Connect API Key

This is the part that confused me the most.

My assumption was simple.

I already had a signing certificate. Surely Electron Builder could just use that to notarize my application.

It can't.

Apple treats signing and notarization as two separate systems.

Your certificate proves who built the application.

Your API key proves that Electron Builder is allowed to submit applications to Apple's notarization servers on your behalf.

Think of it like logging into GitHub.

Your SSH key proves your identity.

Your GitHub Personal Access Token gives a script permission to interact with GitHub automatically.

Apple's API key serves a similar purpose.

Open App Store Connect and sign in using the same Apple ID.

Then navigate to:

Users and Access
↓
Integrations
↓
App Store Connect API

Click the + button.

Give the key any name you like.

Mine is simply called:

Electron Builder

For the role, choose:

Admin

Click Generate.

Apple immediately downloads a file ending in .p8.

This file is extremely important.

Apple will never let you download it again.

If you lose it, you'll have to revoke the key and create a brand new one.

I recommend moving it somewhere permanent instead of leaving it in Downloads.

I created a folder called:

~/Developer/Apple/

and stored the API key there.

Step 7: Understanding The Three Values Apple Gives You

There are three different values involved in notarization.

Every guide mentions them.

Very few explain what they actually are.

The .p8 File

This is your private API key.

Electron Builder reads this file whenever it talks to Apple's notarization servers.

Never commit it to Git.

Never upload it publicly.

Key ID

Every API key Apple creates has a short identifier.

It looks something like:

T3M27XXXXX

This tells Apple which API key Electron Builder is trying to use.

Issuer ID

This one confused me because Apple barely explains it.

The Issuer ID identifies your App Store Connect organization.

It looks like a UUID.

090d627a-xxxx-xxxx-xxxx-c55942bde3df

Think of it as your team's unique identifier.

Electron Builder needs all three pieces before it can authenticate successfully.

Step 8: Configure Environment Variables

Rather than hardcoding credentials inside your project, Electron Builder expects them to be available as environment variables.

Open your shell configuration.

If you're using zsh, that's:

nano ~/.zshrc

Add these three lines.

export APPLE_API_KEY="$HOME/Developer/Apple/AuthKey_XXXXXXXXXX.p8"
export APPLE_API_KEY_ID="XXXXXXXXXX"
export APPLE_API_ISSUER="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Save the file and reload your shell.

source ~/.zshrc

Finally, verify everything loaded correctly.

echo $APPLE_API_KEY
echo $APPLE_API_KEY_ID
echo $APPLE_API_ISSUER

All three commands should print values.

If one of them prints nothing, Electron Builder won't be able to notarize your application.

At this point, all of the Apple setup is finished.

The nice thing is that you only ever have to do this once.

Every future Electron application can reuse the exact same certificate, API key, and environment variables.

The only thing that changes from project to project is your application's bundle identifier.

Step 9: Configure Electron Builder

With all of the Apple setup out of the way, we can finally come back to the Electron project itself.

The nice thing is that the project changes are actually very small.

Most of the work we've done so far has been outside the project. Certificates, API keys, keychains, and environment variables are all part of Apple's ecosystem. Electron Builder simply plugs into it.

Inside your package.json, locate the existing mac section inside the build configuration.

Mine looks like this:

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

Let's quickly go through each option because they're not all obvious.

category

This tells macOS which category your application belongs to.

It affects things like App Store categorization and system metadata. It doesn't change how your application behaves, but it's still good practice to choose the closest matching category.

icon

This should point to your macOS .icns file.

Unlike Windows, macOS uses the ICNS format instead of ICO.

target

Electron Builder supports several output formats.

I build both:

  • DMG for users.
  • ZIP for notarization and updates.

If you're planning on supporting automatic updates later, you'll probably want both anyway.

hardenedRuntime

This one is required.

Apple requires the Hardened Runtime before an application can be notarized.

Without it, notarization simply won't happen.

gatekeeperAssess

Electron Builder recommends leaving this disabled because Gatekeeper verification happens after notarization anyway.

Setting it to false speeds up the build slightly without affecting the final application.

entitlements

Electron applications need a small set of permissions to work correctly under the Hardened Runtime.

Those permissions live inside a separate property list file called:

build/entitlements.mac.plist

We'll create that next.

entitlementsInherit

Electron applications contain a surprising number of nested executables.

Chromium, helper applications, frameworks, renderer processes, GPU processes, and more all live inside the final application bundle.

This property tells Electron Builder to apply the same entitlements to those child components automatically.

notarize

This tells Electron Builder to upload the signed application to Apple's notarization servers after signing completes.

Since we already configured our API key and environment variables, Electron Builder knows how to authenticate automatically.

That's literally all we need.

No custom scripts.

No manual uploads.

No terminal commands after every release.

Step 10: Create The Entitlements File

Next, create a new file called:

build/entitlements.mac.plist

Paste the following contents:

<?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.disable-library-validation</key>
    <true/>

</dict>
</plist>

That's it.

Two permissions.

Nothing more.

Why Does Electron Need These?

Electron uses Chromium.

Chromium uses Google's V8 JavaScript engine.

V8 compiles JavaScript into native machine code while your application is running. This is called Just-In-Time compilation, or JIT for short.

Apple's Hardened Runtime blocks that behavior by default.

The allow-jit entitlement tells macOS that this application legitimately needs to generate executable code at runtime.

The second entitlement allows Electron to load the native libraries that ship with Chromium and any native Node modules your application depends on.

In other words, these aren't random permissions copied from Stack Overflow.

They're required because of how Electron itself works internally.

Step 11: Understanding appId

One thing that confused me was the application's bundle identifier.

You'll already have something like this in your build configuration:

"appId": "com.pinboard.app"

This is your application's unique identifier on macOS.

Every application should have its own unique value.

For example:

com.pinboard.app
com.arcdlp.app
com.myapp.app

The nice thing is that you do not need to register these anywhere.

Apple does not require you to manually create App IDs for standalone Developer ID applications.

Just pick a sensible reverse domain name and keep it consistent.

Once your application is released, avoid changing it.

macOS uses the bundle identifier for application preferences, saved state, and various system caches.

Step 12: Build Your Application

Finally.

Time to build.

If you're building for Apple Silicon:

npm run build:mac

Intel:

npm run build:mac:intel

Universal:

npm run build:mac:universal

Electron Builder now takes over.

Behind the scenes it performs several different jobs.

  1. Packages your application.
  2. Signs every executable inside the application bundle.
  3. Signs the application itself.
  4. Uploads the signed ZIP to Apple.
  5. Waits for notarization.
  6. Staples the notarization ticket.
  7. Builds the final DMG.

Depending on your internet connection and Apple's servers, notarization can take anywhere from a few seconds to several minutes.

Don't panic if the terminal appears to pause for a while.

That's completely normal.

Step 13: Verify Everything Worked

Once the build finishes, don't immediately upload the DMG to your website.

I always like verifying that macOS is happy with the application first.

Open Terminal and run:

spctl -a -vv dist/mac-arm64/MyApp.app

If everything went well, Gatekeeper should report that the application is accepted.

Next, verify the code signature itself.

codesign --verify --deep --strict --verbose=2 dist/mac-arm64/MyApp.app

If this command finishes without errors, your application is correctly signed.

At this point you're ready to distribute it.

Troubleshooting

This is the section I wish I'd found before spending an entire afternoon debugging.

Here are the issues I actually ran into and what eventually fixed them.

"The timestamp service is not available"

This was by far the most frustrating error.

Electron Builder would successfully package the application, start signing it, and then suddenly fail with:

The timestamp service is not available.

My first instinct was to blame my certificate.

Then Electron Builder.

Then the entitlements file.

Then the API key.

None of them were the problem.

After hours of debugging I switched from my home Wi-Fi to my phone's hotspot.

The build completed immediately.

If you're seeing timestamp related errors, don't immediately assume your configuration is wrong.

Apple's timestamp server has to be reachable during signing, and some networks simply don't play nicely with it.

Before changing your project, try another network.

"A timestamp was expected but was not found"

I also saw this error several times.

A timestamp was expected but was not found.

Interestingly, it failed on a different file every single build.

One run would fail while signing one language pack.

The next run would fail somewhere completely different.

That was the clue.

If the same file isn't failing every time, the file probably isn't the problem.

Once again, changing networks fixed it completely.

"notarize options were unable to be generated"

This simply means Electron Builder couldn't find the credentials required for notarization.

Double check your environment variables.

echo $APPLE_API_KEY
echo $APPLE_API_KEY_ID
echo $APPLE_API_ISSUER

If one of those prints nothing, notarization won't start.

"No identity found"

This usually means your signing certificate hasn't been installed correctly.

Verify it exists by running:

security find-identity -v -p codesigning

If your Developer ID Application certificate doesn't appear in the list, Electron Builder won't be able to sign anything.

Can I Reuse This Setup?

Yes.

In fact, that's probably the best part.

Once you've gone through Apple's setup once, every future Electron application can reuse almost everything.

You can reuse:

  • Your Apple Developer membership.
  • Your Developer ID Application certificate.
  • Your App Store Connect API key.
  • Your environment variables.
  • Your entitlements file.

The only thing that changes between projects is the application's bundle identifier, application name, and icons.

That's it.

I now use exactly the same setup for multiple Electron applications without changing anything else.

Frequently Asked Questions

Do I need a different certificate for every app?

No.

One Developer ID Application certificate can sign all of your Electron applications.

Do I need to create a new API key for every project?

No.

The same App Store Connect API key can be reused across all of your projects.

Do I need to register every app inside Apple's Developer portal?

No.

For standalone Developer ID applications, simply choose a unique bundle identifier inside Electron Builder.

Do I need Xcode projects?

Nope.

Electron Builder handles everything.

Can I build Windows and Linux exactly the same way?

Yes.

All of these changes only affect macOS builds.

Your Windows and Linux builds remain exactly the same.

Final Thoughts

I'll be honest.

Apple's signing ecosystem feels intimidating the first time you go through it.

There are certificates, private keys, API keys, Issuer IDs, Key IDs, entitlements, hardened runtime, notarization, stapling, and a lot of terminology that isn't explained particularly well.

The good news is that almost all of it is a one-time setup.

Once everything is configured, building a signed and notarized Electron application becomes as simple as running your normal build command.

Hopefully this guide saves you a few hours of debugging. It certainly would have saved me a few.

If you're still stuck or run into something this guide doesn't cover, feel free to reach out. I'm always happy to help another developer if I can.

You can find me at archisvaze.com.