As you might have read in this recent post, I spent a considerable amount of time the past month working on setting up and building macOS applications based on Electron as the foundation and React for rendering user interfaces, a popular combination.
When starting to build an Electron application, you'll quickly notice that it takes a few important steps to get to a point where you can distribute your final application package to your end-users, be it through a simple download, or even the Mac App Store.
This post will get you set up in no time, enabling you to build state-of-the-art Electron applications, guarded by a type-safe configuration, based on well-maintained libraries. Not only that, but you will also be able to share your packaged applications with friends and users outside of the Mac App Store with ease.
If you want to take the quick path, I've already prepared an example application starter, available here
🔏 Understanding Code Signing and Notarization
For your users to be able to launch the result of your hard work, you will have to go through a process of signing your code, meaning that proof of your application was created by a trusted source, this being your Apple Developer identity, will be attached to the package. This requires you to enroll in the Apple Developer membership, which is absolutely worth the benefits you get, notably being able to distribute without a headache (once everything is configured to work).
To enhance the trust level further, which is already required under most circumstances, you can submit your package to be notarized, which is another process to instruct Apple to check for malicious components early on, as described on their page:
The Apple notary service [...] scans your software for malicious content, checks for code-signing issues, and returns the results to you quickly. [...] the notary service generates a ticket for you to staple to your software [...] also publishes that ticket online where Gatekeeper can find it.
But even before those build steps, there's a lot of small configuration steps involved, from generating icons in multiple sizes for all devices, to attaching machine-readable metadata to make your package more descriptive. And we didn't even get to building the actual application yet.
While all of this might seem like a lot at first, you'll be able to reuse a lot of what we're about to dive in, speeding up further development. Most of the aforementioned processes only need to be configured once as well, so after the initial setup, you're good to go!
🌅 Setting up your development environment
As a first step, you'll need to enroll in the Apple Developer Program. This is necessary to be able to manage certificates and distribute your application later on. You'll have to enter a few personal details and pay the yearly fee upfront. Once that's done, you mastered the most difficult step already! Now, let's get to preparing your environment.
To manage your certificates, profiles, and identities, you need to have Xcode installed on your machine. This will also install utilities for code-signing, notarization, and other required dependencies. You can download Xcode from the Mac App Store.
While Xcode is installing, you can already generate an application-specific password, which you'll use to notarize your application later on. This is required as you need to sign in with your Apple ID. To create the password, you can follow this official guide.
Once Xcode is installed, launch it and enter the preferences (⌘,
), then navigate to Accounts, your team, and click on Manage Certificates...
. We need to create a Developer ID Application
certificate tied to your Apple
developer account, which allows you to sign a macOS app before distributing it outside the Mac App Store.
After the certificate is created, we need to retrieve its identifier, which our code-signing step depends on to know which identity to use. In your terminal, you can run the following command to list all code-signing certificates, and copy the string contents of the one you just created:
$ security find-identity -v -p codesigning
1) abc... "Developer ID Application: Bruno Scheufler (xyz...)"
1 valid identity found
To recap the previous steps, we've now
- set up your Apple Developer account
- created an application-specific password for notarization
- installed Xcode with its components
- created a code-signing certificate
Now the real development can begin!
🚀 Getting Started
After downloading the application starter (you can either clone the repository or download
it as zip archive), you can head to the electron-macos
directory, where
we've got everything prepared for your Electron application. Let's go over the basics!
Due to the complexity of the various building and packaging steps involved from development to distribution, I based this configuration on Electron Forge, which glues together popular and well-maintained libraries such as Electron Packager and Electron OSX Sign to provide an all-in-one development lifecycle, hiding the different parts going on under the hood.
Of course, we need to configure details such as the application name, icons, and other settings relevant to building and distributing our application ourselves. This is why we're using a custom configuration file,
stored in src/forge.config.ts
. As this is written in TypeScript to get all the benefits of the types provided by the aforementioned libraries, this file will be transpiled to regular JavaScript during the build process.
🏗 Configuring building and packaging steps
You're free to play around with the configuration and customize it to your liking, for example changing app name and details to creating an app icon, overwriting the current one stored in icon.png
. As we're generating
the icon set that macOS expects (including different sizes for specific places it will be used in) using a custom script, we expect application icons to be a 1024x1024
PNG file, which will be processed by icons.sh
during
the build phase.
If you're pulling in other external files, make sure to update the ignore
function accordingly. This is used to make sure we're not
adding a bunch of unwanted and unnecessary bloat to our application package.
Continuing with code-signing and notarization configuration, you'll notice that we're using a set of environment variables, notably APPLE_CODE_SIGN_IDENTITY
, APPLE_ID
, and APPLE_ID_PASSWORD
.
This refers to the certificate identity you copied before, your Apple ID email, and the application-specific password we generated earlier. You can place all of those values in a .env
file, or set
them manually before building. If you omit a variable, code-signing will be skipped, and you won't be able to distribute the application.
🤩 Developing, building, and packaging your application
We haven't talked about your application itself yet! All code around the main process currently lives in src/index.ts
, which manages the lifecycle of your application from launch to termination.
For this starter, we're loading the actual page remotely, so using an URL of a live page, instead of bundling the site itself. Especially for offline-first applications, this might be undesirable, but you can easily include your built pages in the package and load it as a file, I'll probably write a guide for this.
To start your application for regular development, run
yarn start
This will build the TypeScript files (such as your main process and Forge configuration) and generate the icon set, after which we'll start up Electron Forge in development mode.
To build, package, and sign your application, essentially running all preparation steps for distribution, run
yarn make
🛤 Going beyond
There's still a lot to cover. Bundling your frontend for offline availability, setting up automatic updates, configuring release workflows, all those steps will get your application and development flow to the next level. I think every single topic deserves its own piece of content, simply because there's a lot to consider. I like to view this post as the first part of a series of guides around building Electron applications, specifically targeted for distribution on macOS.
I hope you enjoyed this post! If you've got any questions, suggestions, or feedback in general, don't hesitate to reach out on Twitter or by mail.