Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save steve981cr/def310670dfd9ed1439bf31cc734f941 to your computer and use it in GitHub Desktop.
Save steve981cr/def310670dfd9ed1439bf31cc734f941 to your computer and use it in GitHub Desktop.
Step by step instructions on how to release an Electron app on the Mac App Store. Uses Electron-builder to package the app.

How to Release an Electron App on the Mac App Store

By Steve Carey - Last updated September 6, 2023. Originally published Feb 4, 2020.

Super basic app example: Github electron-app-store-example
To Do List app example (contains native node modules): github.com/steve981cr/electron-todo-example

Table of Contents

Introduction
Step 1) Start with your completed Electron Application
Step 2) Apple Developer Account
Step 3) Create Three Signing Certificates
Step 4) Create an App ID
Step 5) Add the App to your developer account
Step 6) Generate two Provisioning Profiles (AppleDevelopment and MacAppStore)
Step 7) Entitlements
Step 8) Create an icon set
Step 9) package.json file
Step 10) Build a development and distribution versions of the App with electron-builder
Step 11) Test the app
Step 12) Upload the pkg file
Native Node NPM packages
Release an update


Introduction

You have three options for distributing an Electron App for MacOS. Through the Mac App Store (MAS), through a third party platform like GitHub Marketplace, or directly (e.g, download from your website). Apple prefers you distribute through the Mac App Store. Partly because they review all apps before they can be put on the Mac App Store so end users can feel confident that the app is safe. And partly because they take a cut of app revenue purchased through the MAS. This tutorial focuses on the steps required to put your app on the MAS.

All apps distributed though the MAS must be sandboxed, meaning they are completely self contained except for any approved entitlements.

There are a few options for packaging the app. We will use Electron-Builder.

You will need to package two versions of your app. A development version for testing, and a distribution version for submission to Apple.

Apple Reference: developer.apple.com/app-store/review
developer.apple.com/app-store/review/guidelines
Electron Reference: electronjs.org/docs/tutorial/mac-app-store-submission-guide
Electron-builder Reference: electron.build/configuration/mac
  and electron.build/configuration/mas


Step 1) Start with your completed Electron Application

Electron should be a development dependency: npm install -D electron


Step 2) Apple Developer Account

2a) You need an Apple Developer Account. Costs ~$100/year. Sign up at developer.apple.com.

2b) And sign up for the App Store's Small Business Program (for developers with revenue under $1 million/year): developer.apple.com/app-store/small-business-program
If approved, Apple's commission drops from 30% to 15%.

2c) Add your device to your Developer account developer.apple.com/account/resources/devices/list

  • Click the + to add a device.
  • To get the Device ID from your Mac: open the System Information app > Copy the Hardware UUID.

Step 3) Create Three Signing Certificates

Ref: Apple certificate types: https://help.apple.com/xcode/mac/current/#/dev80c6204ec

  • Apple Development: used to sign apps for development and testing on machines that have been registered on the Apple Developer website.
  • Apple Distribution: used to sign apps submitted to the Mac App Store.
  • Mac Installer Distribution: used to sign the Mac Installer Package in addition to the app itself.

You can create the signing certificates with either XCode or using the Keychain Access app.

Using XCode (preferred):

Create and install these three certificates with XCode. Repeat the below process for each certificate.

Open XCode > Click the XCode menu > Select Preferences > Click Accounts > Click the Manage Certificates button > Click the + button in the lower left corner > Select the certificate type to create (Apple Development, Apple Distribution, Mac Installer Distribution).

Using the Keychain Access app:

Alternatively, you can create and install these three certificates with the Keychain Access app. Repeat the below two-step process for each certificate.

Keychain Access App - Step 1) Request a certificate:

  • Launch Keychain Access located in /Applications/Utilities.
  • Click the Keychain Access menu > Certificate Assistant > Request a Certificate from a Certificate Authority.
  • In the Certificate Assistant dialog, enter an email address in the User Email Address field.
  • In the Common Name field use the value provided.
  • Leave the CA Email Address field empty.
  • Choose "Saved to disk" saving it to the desktop, and click Continue. Filename is CertificateSigningRequest.certSigningRequest

Keychain Access App - Step 2) Import the new certificate from your developer account to your Keychain:

  • go to https://developer.apple.com/account > Certificates > + (to add a new certificate) > Select the certificate type (Apple Development, Apple Distribution, Mac Installer Distribution) > click the continue button > Choose File > Double-click the certificate file you downloaded above > Download the new certificate file > Double click the downloaded file and it will be automatically loaded to your keychain.
  • Delete the CertificateSigningRequest.certSigningRequest and the certificate download files from the desktop.

View the certificates:

  • To view the certificates in XCode: Open XCode > Click the XCode menu > Select Preferences > Click Accounts > If you have multiple apps select the relevant appleID and Team > Click the Manage Certificates button - This will show your list of certificates.

  • To view the certificates in your keychain: Open the Keychain Access app in the Application Utilities folder > Click on the Login Keychain (should be selected by default) > Select Category: MyCertificates - this should display your certificates along with your Team Name and ID number. For a solo developer your team name is just your name.

  • To view the certificates in you developer account go to developer.apple.com > Certificates, IDs, & Profiles menu > Certificates menu > Development, Distribution and Mac Installer Distribution type certificates should be listed there. Or go directly to https://developer.apple.com/account/resources/certificates/list

  • To view the certificates from the command line run the below. The -v option is for valid identities only: security find-identity -v


Step 4) Create an App ID

  • Go to your developer account - developer.apple.com > Certificates, IDs & Profiles > Identifiers > + (to add a new id) > select App IDs > continue button.
  • On the Register App ID page:
    • Platform: MacOS
    • Bundle ID: Explicit. Apple recommends using a reverse-domain name style string (i.e., com.domainname.appname). This does not need to correspond with an actual website. This will need to match the appId property in the package.json file.
    • When done click continue, review it, then click register.

Step 5) Add the App to your developer account

Reference: developer.apple.com/help/app-store-connect
Official instructions for adding an app: developer.apple.com/help/app-store-connect/create-an-app-record/add-a-new-app

  • Go to appstore connect

  • Click on My Apps > + (to create a new app) > select New MacOS App > Select the appId you registered previously.

  • Fill out the App Info section:

    • Enter the name you want your app to appear on the Mac App Store as. Two apps can't have the same name so your preferred app name may be taken.
    • Your app needs a privacy policy URL and (in another section) a support URL.
    • Select the appropriate category for your app. See Category list.
    • Add a license agreement. You can use Apple's Standard License Agreement apple.com/legal/internet-services/itunes/dev/stdeula
  • Fill out the Pricing and Availability section:

    • Select the price you want to charge for your app. Be aware that Apple keeps 30%, or 15% if you enroll in the Small Business Program.
    • You can also select specific countries to make your app available in. Default is the whole world.
  • Fill out the MacOS section:

    • For marketing you can add up to 10 screenshots and up to 3 15-30 second videos. Be aware that these must be in specific dimensions.
    • The App Store Icon is taken from your app's icon.icns file so you don't need to upload them separately.
    • Enter the version number using semantic versioning (e.g., starting with 1.0.0).
    • If your app requires any entitlements enter them here along with the explanation as to why you need that entitlement. The app will be rejected if you request entitlements you don't need. See the Entitlements section below for details.
    • Enter the support URL (required) and a marketing URL (optional).
    • When you upload your app (using the Transporter App - discussed below) you need to select it here.
    • You can add attachments that may be useful to the reviewer such as a video of how to use the app (can be any dimensions).

Step 6) Generate two Provisioning Profiles (AppleDevelopment and MacAppStore)

  • Go to your apple developer account profiles page developer.apple.com/account/resources/profiles/list
  • These expire after one year, so if you are updating your app, run the command at the bottom of 9a and 9b to see the expiration date. If they are expired you need to recreate them.

6a) Generate the AppleDevelopment Provisioning Profile

  • Click the + sign to add a new profile which will take you to the "Register a New Provisioning Profile" page.
  • Select "macOS App Development". Then click Continue.
  • Select your appID, then click continue.
  • Give it a recognizable name like "AppleDevelopment", then download it to your computer.
  • Double click the file to install it, then place it in the project's build directory. The file name would be AppleDevelopment.provisionprofile. This corresponds to the provisioningProfile key in the package.json file. Whatever you name it, during the build process it gets renamed to embedded.provisionprofile.
  • This is a binary file. If you want to read it's contents in XML format and you have X-Code's Command Line utilities installed, from the Terminal in the project folder run the below command: security cms -D -i build/AppleDevelopment.provisionprofile

6b) Generate the MacAppStore Provisioning Profile

  • Click the + sign to add a new profile which will take you to the "Register a New Provisioning Profile" page.
  • Select Mac App Store. Then click Continue.
  • Select your appID, then click continue.
  • Select your Mac App Distribution certificate, then click continue.
  • Give it a recognizable name like "MacAppStore", then download it to your computer.
  • Double click the file to install it, then place it in the project's build directory. The file name would be MacAppStore.provisionprofile. This corresponds to the provisioningProfile key in the package.json file. Whatever you name it, during the build process it gets renamed to embedded.provisionprofile.
  • This is a binary file. If you want to read it's contents in XML format and you have X-Code's Command Line utilities installed, from the Terminal in the project folder run the below command: security cms -D -i build/MacAppStore.provisionprofile

Step 7) Entitlements

Reference: electronjs.org/docs/tutorial/mac-app-store-submission-guide

  • Make parent and child entitlement property list files using XML. Put them in the build folder.
  • These files correspond to the entitlements and entitlementsInherit keys in the package.json file.

The parent entitlement file:

/build/entitlements.mas.plist

<!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.app-sandbox</key><true/>  
    <key>com.apple.security.application-groups</key>  
    <array> 
      <string><TEAM_ID.com.companyname.appname></string>  
    </array>  
    <!-- Put any entitlements your app requires here. Below is an example -->  
    <key>com.apple.security.files.user-selected.read-write</key><true/> 
  </dict> 
</plist>  

Child entitlement file (inherits from the parent):

/build/entitlements.mas.inherit.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.app-sandbox</key><true/>  
    <key>com.apple.security.inherit</key><true/>  
  </dict> 
</plist>  
  • First thing to notice is app-sandbox is set to true. All apps in the MAS must be sandboxed. That means they don't have access to resources outside the app, unless you specifically request an exception (an entitlement) and it is approved. For example to be able to read and write to files in the user's computer (outside the app) you need to request user-selected files read-write access. If approved your app can read/write files that users have specifically opened or saved using the mac Open or Save dialog box.
  • These entitlements must correspond to the entitlements you request for your app in Appstoreconnect.apple.com
  • Read about entitlements here.
  • The list of available app sandbox entitlement keys are here.
  • The application-groups key is an array containing your app identifier (TeamID and appId).

Step 8) Create an icon set

Reference: electron.build/icons | developer.apple.com/design/human-interface-guidelines/app-icons

  • You need to create your icon in at least two different sizes: 512x512 px and 1024x1024 px saved as png files. But ideally use all the Apple recommended sizes which additionally include 16x16, 32x32, 64x64, 128x128 and 256x256.
  • This is a useful guide: How to create high resolution icns files. A few things to highlight:
    • Make a folder called icon.iconset (or any name that has .iconset as the extension) to hold the images.
    • Use Apple's naming convention for the image files (e.g., icon_512x512.png, icon_512x512@2x.png, etc.).
    • The @2x files are double the size stated in terms of pixels. However in terms of display, Apple just doubles the density of the pixels instead of doubling the width and height. As such, some of the files will be the same image dimensions but two different files (e.g., icon_256x256@2x.png and icon_512x512.png are two separate files but are the same image size). If you are simplifying the images at smaller sizes, make sure the @2x image is the same as the 1x, just at double the size.
    • When you get down to the small sizes such as icon_16x16.png you'll likely need to simplify the image, otherwise it will just look blurry.
    • Convert the iconset into an icns file in the Terminal from the directory holding the icon.iconset folder with the png images. Enter the iconutil command: iconutil -c icns icon.iconset
    • Put the resulting icon.icns file into the build folder of your electron project.

Step 9) package.json file

Electron-builder API lists the build properties: https://www.electron.build/configuration/mac.html

9a) Local version

You can build a local version of your app not for distribution, by excluding the "mac" key from the package.json file. This will build a packaged version of your app, a zipped version, and a dmg installer.

The values in angled brackets need to be changed to your info then remove the brackets. The postinstall script is only needed if your app uses native node dependencies (see Native Node NPM packages at the end).

{ 
  "name": "<AppName>",  
  "version": "<1.0.0>",
  "scripts": {
    "start": "electron .",
    "dist": "electron-builder",
    "postinstall": "electron-builder install-app-deps"
  },
  ...
  "author": "<AuthorName>", 
  "build": {  
    "appId": "<com.companyname.appname>",
    "productName": "<App Name (can include spaces & special characters)>", 
    "buildVersion": "<1.0.0>",
    "copyright": "Copyright © 202X <Developer or Company Name>",
    "files": [
      "!<file-or-directory-name-to-exclude>"
    ]
  }
  ...
}

9b) MAS Development version (for testing)

This is a test version of the MAS build, which you can run on your development computer.

{ 
  "name": "<AppName>",  
  "version": "<1.0.0>",
  "scripts": {
    "start": "electron .",
    "dist": "electron-builder",
    "postinstall": "electron-builder install-app-deps"
  },
  ...
  "author": "<AuthorName>", 
  "build": {  
    "appId": "<com.companyname.appname>",
    "productName": "<App Name (can include spaces & special characters)>", 
    "buildVersion": "<1.0.0>",
    "copyright": "Copyright © 202X <Developer or Company Name>",
    "files": [
      "!<file-or-directory-name-to-exclude>"
    ],
    "mac": {  
      "category": "public.app-category.<categoryName>", 
      "icon": "build/icon.icns",  
      "target": "mas-dev",
      "type": "development",
      "hardenedRuntime": false, 
      "provisioningProfile": "build/AppleDevelopment.provisionprofile",
      "entitlements": "build/entitlements.mas.plist",
      "entitlementsInherit": "build/entitlements.mas.inherit.plist"
    }
  }
  ...
}

9c) MAS Distribution version (for submission to the Mac App Store)

This builds a pkg installer for submission to the MAS. It changes the target, type, and provisioningProfile from 7b above.

{ 
  "name": "<AppName>",  
  "version": "<1.0.0>",
  "scripts": {
    "start": "electron .",
    "dist": "electron-builder",
    "postinstall": "electron-builder install-app-deps"
  },
  ...
  "author": "<AuthorName>", 
  "build": {
    "appId": "<com.companyname.appname>",
    "productName": "<App Name (can include spaces & special characters)>", 
    "buildVersion": "<1.0.0>",
    "copyright": "Copyright © 202X <Developer or Company Name>",
    "files": [
      "!<file-or-directory-name-to-exclude>"
    ],
    "mac": {  
      "target": "mas",
      "type": "distribution",
      "hardenedRuntime": false,
      "category": "public.app-category.<categoryName>",
      "icon": "build/icon.icns",
      "provisioningProfile": "build/MacAppStore.provisionprofile",
      "entitlements": "build/entitlements.mas.plist",
      "entitlementsInherit": "build/entitlements.mas.inherit.plist"  
    }
  }
  ...
}

Only include the postinstall script above if you have native dependencies that need to be recompiled by Electron (see Native Node NPM packages below).

Reference:

package.json keys:

  • Name and productName: The name key will be used as the display name for your app on the user's computer. It cannot contain spaces or special characters. If you want to use spaces or special characters in the name then add a productName key within the build key. You set the name of your app as it appears in the Mac App Store at appstoreconnect.apple.com.
  • Version number and build number: The version number in package.json should correspond with the version number of your app at appstoreconnect.apple.com. If you upload your app to appstoreconnect but decide to make changes before submitting it for review, you can't delete what you uploaded. Rather you submit a revised app with a different build number. The build number defaults to the version number. To set a different build number, use the buildVersion property.
  • Use the same appId as you created for your app at developer.apple.com.
  • files: You can add an array of file and directory names to exclude from the build. Preface each name with a "!" to indicate that the file/directory should be excluded. Ref: electron.build/file-patterns.
  • The category key should align with the category you set for your app in appstoreconnect.apple.com. Mac uses this key in the Finder via View > Arrange by Application Category when viewing the Applications directory. List of possible categories.
  • Set your target to "mas-dev" if building the development version (for testing) and "mas" if building the distribution version (to submit to the MAS).
  • Set Hardened Runtime to false. You would set it to true if you want to distribute the app outside the MAS. For more on hardened runtime see developer.apple.com/documentation/security/hardened_runtime
  • Set type to "development" for the development version, "distribution" for the MAS version.

Keys you don't need to set because the default is already correct:

  • The icon key points to where your icon.icns file is. The default folder and filename is build/icon.icns so you can leave it out if it is located there.
  • The type key can be distribution or development. Distribution is the default so you can leave this key out.
  • The identity key sets the name of the certificate to use when signing. However, for the MAS build, the signing certificate will be taken from the provisioning profile (covered below). And Electron-builder will automatically use the 3rd Party Mac Developer Installer certificate in your Keychain to sign the pkg file.
  • GatekeeperAssess key is set to false by default. Mac's Gatekeeper ensures that apps distributed outside the app store are signed and notarized. Since this build is for the MAS you can leave this at false.

Step 10) Build a development and distribution versions of the App with electron-builder

  • Install electron-builder as a development dependency: npm install -D electron-builder
  • Optionally, you can build an unsigned local version of your app that is not packaged for the Mac App Store. Use the 9a version of the package.json file.
  • Build the MAS-development version of the app using the 9b version of package.json above, which uses the AppleDevelopment.provisionprofile, and the Development signing certificate. Use this to test the MAS version on your computer. npm run dist
  • After testing the Development version of the app, build the MAS distribution version and .pkg installer. Use the 9c version of package.json, the MacAppStore.provisionprofile, and the Distribution and Mac Installer Distribution signing certificates. npm run dist

If you get a "command not found" error, then include the relative path. Here are the direct commands with the path:

Mac: ./node_modules/.bin/electron-builder

Windows: .\node_modules\.bin\electron-builder.cmd


Step 11) Test the app

Test the Development version of the app before building the distribution version. Make sure it works as expected.

Then build the distribution version. This version will crash and give you a signing error if you try to open it on your computer. It must be re-signed by Apple to be able to run, which will only be possible after being downloaded from the Mac App Store. However, there are some checks you can make before submitting it to Apple.

  • You can test the signature if you have X-Code's CLI utilities installed. To check that your app is signed, go to the directory holding your app (e.g., MyApp/dist/mas) and enter the below command. If the app is signed it will display the details in the Terminal. codesign --display --verbose=2 <AppName>.app
  • To check that the .pkg file is signed, enter the below command from the .pkg file's directory. If it is signed it will display the details in the Terminal. pkgutil --check-signature <AppName>-1.0.0.pkg
  • The Transporter app (see next section) has built-in checks on your app that will let you know about some (but not all) problems with your app. Simply load the app in the Transporter and it will do several checks. If everything is okay, you can click the deliver button to send it to appstore connect. Then you can submit it for review.
  • If you are getting errors you could start by making sure all the basics are working by using this process on a simple Electron app like the Electron App Store Example app on Github. Just add in your developer and app datails and provisioning profile.

Step 12) Upload the pkg file

  • There are three different tools you can use to upload the pkg file: developer.apple.com/help/app-store-connect/manage-builds/upload-builds.
  • The preferred tool is the Transporter app which can be downloaded free from the Mac App Store.
  • Open the Transporter app > Drag and drop your Electron app's pkg file into the Transporter app.
  • It will check for errors. If no errors found click the Deliver button to send the app to your developer account.
  • Go to appstoreconnect.apple.com and click MyApps > Prepare for Submission > Build + icon > Select the uploaded file - Done
  • Note: the build may have a "Missing Compliance" warning next to it. If so, when you submit the app you will be asked about any encryption in your app, so just answer the questions given. See electronjs.org/docs/latest/tutorial/mac-app-store-submission-guide#cryptographic-algorithms-used-by-electron
  • If you are ready to submit the app for review then click "Submit for review".
  • If you decide to make additional changes before submitting for review, you have to load another .pkg file through the Transporter app with a higher build number (e.g., 1.0.1).

Native Node NPM packages

Reference: electronjs.org/docs/latest/tutorial/using-native-node-modules
To Do List app example (contains native node module): github.com/steve981cr/electron-todo-example

Native node modules are npm packages that include C or C++ code.

Recompile native Node dependencies for Electron

C/C++ code must be compiled into executable binaries. Even if the modules are precompiled when installed, they must be recompiled for Electron.

To recompile native modules in development, you can add the below script to your package.json file:

"postinstall": "electron-builder install-app-deps"

After installing native modules, run the script: npm run postinstall

If you don't recompile native modules, and try to run the app, you will get an error that says something like:

Error: The module '/path/to/native/module.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION $XYZ. This version of Node.js requires
NODE_MODULE_VERSION $ABC. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).

Code Sign native Node dependencies

Electron builder compiles the app into a format called ASAR (stands for Atom Shell Archive Format) Ref: https://www.electronjs.org/docs/latest/glossary#asar

Native modules must be pulled out of your built binary app and code signed separately.

In the build script add an asarUnpack property. The value is an array where uou list all files that need to be pulled out. The files we need to pull out are binary files that have a .node extension. Use wildcards for the directory and filename:

"build": {
  ...
  "mac":
    ...
    "asarUnpack": ["**/*.node"]
  }
}

The file will be placed in your app file in Contents/Resources/app.asar.unpacked/node_modules/

Electron-builder will automatically sign this binary when you run the dist command.

Its binary file will be pulled out of your app's asar binary file. You can confirm that it is code signed with the below command. Replace with the name of your app, and use the correct path to the .node file: codesign --display --verbose=2 dist/mas-dev/<AppName>.app/Contents/Resources/app.asar.unpacked/node_modules/path/to/filename.node



Release an update

To submit an updated version of your app to the Mac App Store, make sure you have unexpired versions of the following:

Apple Certificates:

Certificate Use Expires
Apple Development MAS dev Expires in one year
Apple Distribution MAS Expires in one year
Mac Installer Distribution MAS installer Expires in one year

View your certificates: https://developer.apple.com/account/resources/certificates/list
Or from the command line: security find-identity -v


Provisioning Profiles

File Use Expires
AppleDevelopment.provisionprofile MAS dev Expires in one year
MacAppStore.provisionprofile MAS Expires in one year

View/Add: https://developer.apple.com/account/resources/profiles/list

Put the provisioning profile files in the build/mac directory.

To read the files enter: security cms -D -i build/mac/AppleDevelopment.provisionprofile

security cms -D -i build/mac/MacAppStore.provisionprofile


Entitlements:

Entitlements Use Expires
entitlements.mas.plist MAS no expiration
entitlements.mas.inherit.plist MAS no expiration

Put your entitlement files in the build directory.


Modify, package and sign the App

In your updated Electron App:

  • Change the version and buildVersion numbers in the package.json file. Use semantic versioning (e.g., 1.0.1).
  • Ideally, upgrade to the latest versions of all your npm packages including electron and electron-builder.

Package and code sign the app and installer by running the script npm run dist

Confirm that the app and pkg installer are signed:
Development version:
codesign --display --verbose=2 dist/mas-dev/<AppName>.app
Distribution version:
codesign --display --verbose=2 dist/mas/<AppName>.app
pkgutil --check-signature dist/mas/<AppName>-1.0.0.pkg


Submit the app to App Store Connect

Go to the app in your Apple Developer account: https://appstoreconnect.apple.com/apps Click on your app.
Click the + sign in the left panel to add a new version.
Enter the new version number that matches the version in the package.json file.
Fill out: What's New in This Version.
Submit the new app version to App Store Connect with the Transporter app.
Click Submit for Review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment