What It Means to Distribute Directly with Developer ID

There are broadly two ways to get a macOS app into users’ hands. One is through the Mac App Store (MAS), and the other is direct distribution — letting users download a .dmg (or .app) file you build yourself from a website, GitHub, or similar.

Direct distribution has clear advantages. You don’t have to wait for App Store review, there are no payment commissions, and you can ship updates whenever and however you like. In exchange, the things the App Store used to handle for you — code signing, notarization, and automatic updates — are now yours to set up.

If you don’t set these up, macOS’s security mechanism Gatekeeper will block the app from launching. When a user first opens the app, they see a warning like “this app cannot be opened because it is from an unidentified developer,” and most users give up on installing at that point. For the app to open with a simple double-click like any properly distributed app, you need to sign the app with a Developer ID certificate and have it notarized by Apple.

This series covers that one-time setup process. One bit of good news: most of the setup covered here is done only once and reused across every release.

What You’ll Build in This Series

Over three parts, you’ll put the following in place.

  • (Part 1, this article) A Developer ID Application certificate + credentials for notarization
  • (Part 2) An EdDSA signing key for Sparkle automatic updates
  • (Part 3) A public repository to host the update feed + finishing the build settings

By the end of the series, you should have the following in hand. For now, just skim the list; each part will fill in one item at a time.

  • A Developer ID Application certificate in the macOS Keychain
  • A notarization credentials profile stored in the Keychain
  • An EdDSA key pair for signing Sparkle updates (public key + private key)
  • A backup of the private key in a safe place
  • A public GitHub repository + GitHub Pages to host the update feed (appcast)
  • The build settings file (ExportOptions.plist) and app-side configuration

The Example App — FocusTimer

This series uses a fictional macOS app, FocusTimer (a simple timer app for managing focus time), as its running example. The FocusTimer name, the bundle identifier com.example.FocusTimer, the domain example.com, the certificate name, the Team ID, and so on that appear in commands and paths are all example values. When you actually follow along, swap them out for your own app name and account details.

This article is written for Xcode 26 and Sparkle 2.x (the automatic-update framework, which appears in Part 2). Apple often changes the layout of its screens and the placement of menus, so even if a button name is slightly different, assume the overall flow is the same.

Prerequisites — Command-Line Tools

First, install two command-line tools needed for build and release automation. This assumes you already have the macOS package manager Homebrew installed.

brew install gh create-dmg
  • gh — GitHub’s official CLI. You’ll use it later to create GitHub Releases.
  • create-dmg — A tool for building a .dmg disk image for distribution. It also generates the screen that guides users to drag the app into the Applications folder.

You won’t use these right away, but release-automation scripts are often designed to check for the presence of these tools first and stop immediately if they’re missing, so it’s worth installing them in advance.

Step 1 — Verify Your Apple Developer Program Membership

To issue a Developer ID certificate and notarize an app, you need an Apple Developer Program membership. For an individual account, it’s a paid program costing $99 per year.

If you’re already enrolled, you just need to confirm it’s active.

  1. Go to developer.apple.com/account
  2. In the Membership details section, confirm the status is Active
  3. On the same screen, make a note of your Team ID (in the examples, ABCDE12345). It’s used throughout the later steps.

If you haven’t enrolled yet, membership approval usually takes about a day. Certificates can’t be issued before approval, so it’s best to handle this first.

Step 2 — Issue a Developer ID Application Certificate

The Developer ID Application certificate is the certificate used to sign the .app and .dmg you distribute. macOS Gatekeeper recognizes an app signed with this certificate as “an app made by a known developer.”

Apple also has a separate Developer ID Installer certificate for signing .pkg installer packages. Since this series distributes via .dmg, we only deal with the Application certificate.

Issuing Procedure

The simplest way is through Xcode.

  1. Launch Xcode → menu Xcode → Settings… (⌘,)
  2. Select the Accounts tab → click your Apple ID in the left-hand list
  3. Click the Manage Certificates… button at the bottom right
  4. In the new window that opens, click the + button at the bottom left
  5. Select Developer ID Application from the menu
  6. After a second or two, a new certificate appears in the list — click Done

A certificate issued this way is automatically stored in the macOS Keychain. The certificate and its corresponding private key are stored together as a pair, and this private key is what makes signing possible.

Verifying the Issuance

In the terminal, run the following command to confirm the certificate was installed correctly.

security find-identity -v -p codesigning | grep "Developer ID Application"

If a single line like the following appears, it worked.

  1) A1B2C3D4E5F6...  "Developer ID Application: Gildong Hong (ABCDE12345)"

The string inside the quotes is the certificate’s official name. Gildong Hong is the name registered on the Apple account, and ABCDE12345 in parentheses is the Team ID you noted in Step 1. This name is used as-is later when automating code signing, so double-check it now.

Renewing the Certificate

A Developer ID certificate is valid for 5 years. As expiration approaches, it will be marked Expired in Xcode’s Manage Certificates list. At that point, just issue a new certificate using the exact same procedure as above.

The good news is that apps already signed and distributed with the old certificate are not invalidated the moment it expires. Only newly created builds going forward need to be signed with the new certificate. So there’s no need to worry too much about expiration.

Step 3 — Register an App-Specific Password for Notarization

What Is Notarization?

Notarization is the process of uploading your app to Apple’s servers before distribution to have it scanned for malware. If it passes the scan, Apple issues a “notarization ticket,” and that ticket must be attached to the app for Gatekeeper to open it without warnings. If signing proves “who made it,” notarization is a separate process that proves “Apple has scanned it once.”

Notarization submission uses Apple’s notarytool command, which asks for your Apple ID password every time you submit. To avoid entering it each time, you create an app-specific password and store it in the Keychain ahead of time.

3-1. Issue an App-Specific Password

An app-specific password is a 16-character password used only for a specific purpose, in place of your main Apple ID password.

  1. Go to appleid.apple.com → sign in with the Apple ID enrolled in the Apple Developer Program (in the examples, [email protected])
  2. Go to Sign-In and Security → select App-Specific Passwords
  3. Click the + button → enter a name for the password (e.g., focustimer-notarize)
  4. Click Create → enter your Apple ID password once more
  5. A 16-character password in the format abcd-efgh-ijkl-mnop appears on screen.

Once you close this screen, you cannot view the password again. Before moving to the next step, copy it briefly into a password manager or similar.

3-2. Store It in a notarytool Profile

Save the password you received as a Keychain profile. In the command below, replace the --password value with the actual password you just received before running it.

xcrun notarytool store-credentials "focustimer-notarize" \
  --apple-id "[email protected]" \
  --team-id "ABCDE12345" \
  --password "abcd-efgh-ijkl-mnop"
  • First argument "focustimer-notarize" — the name to give this profile. From now on, you’ll load the credentials by this name when notarizing.
  • --apple-id — the email of the Apple Developer Program account
  • --team-id — the Team ID from Step 1
  • --password — the app-specific password issued in 3-1

If the following output appears, it worked.

Credentials saved to Keychain.

Now that the credentials are stored in the Keychain, you no longer need the original app-specific password. (That said, if you plan to build on other computers too, it’s convenient to keep it in a password manager.)

3-3. Verification

Check that the saved profile actually works.

xcrun notarytool history --keychain-profile "focustimer-notarize"

If the message Successfully received submission history. appears, everything is fine. The submission history below it may be empty (which is normal, since you haven’t notarized anything yet) or may contain prior records.

A Key Pitfall — Your Apple ID and GitHub Account Are Different

This is the spot where people setting up direct distribution for the first time get stuck most often.

  • Apple Developer Program account — used for issuing certificates and for notarization (example: [email protected])
  • GitHub account — used in Part 3 to host the update files (example: [email protected])

In many cases, these two are different emails. In particular, the --apple-id of notarytool store-credentials must be the Apple Developer account email. If you enter the GitHub account email, authentication fails — and the error message isn’t friendly, so it’s hard to track down the cause.

If the two accounts’ emails are easy to confuse, we recommend making a note of which one is for Apple and which is for GitHub. This distinction comes up repeatedly throughout the series.

Part 1 Wrap-Up

If you’ve followed along this far, you now have the following.

  • ✅ Command-line tools for release automation (gh, create-dmg)
  • ✅ An active Apple Developer Program membership
  • ✅ A Developer ID Application certificate stored in the Keychain
  • ✅ A notarization credentials profile stored in the Keychain (focustimer-notarize)

With this, you’re ready to sign and notarize the app. But almost no app is delivered to users just once and then done. You have to keep shipping new versions that fix bugs and add features. On the App Store, automatic updates are handled for you, but with direct distribution, this too is something you have to set up yourself.

In the next part, we’ll create the EdDSA signing key for Sparkle, the de facto standard automatic-update framework for macOS apps. It’s a verification mechanism dedicated to update files — yet another layer, separate from the certificate.