Distributing macOS Apps: App Store vs Notarization vs Homebrew

Shipping a macOS app means choosing a distribution channel. The three main options — Mac App Store, direct notarized distribution, and Homebrew — have different requirements, costs, and user expectations.

This guide compares them across code signing, sandboxing, updates, pricing, discoverability, and the developer experience of each.

Mac App Store

The App Store is the most restrictive channel and the one with the most built-in infrastructure.

Requirements

  • Apple Developer Program membership ($99/year).
  • Sandboxing is mandatory. Restricted capabilities the app needs (network, file access, camera, microphone) must be declared in an entitlements file.
  • App Review. Every submission — initial and updates — goes through human review. Rejections are common for sandboxing violations, incomplete metadata, or UI that does not match HIG guidelines.
  • Code signing with an Apple-issued certificate.
  • Purchase validation for paid apps. Use AppTransaction where available, or receipt validation when you need to support older APIs.

Sandboxing in practice

Sandboxing is the most common source of friction. The app runs in a container with minimal default access. Common capabilities and their entitlements:

Capability Entitlement
Read files or folders chosen in an open dialog com.apple.security.files.user-selected.read-only
Read/write the user’s Downloads folder com.apple.security.files.downloads.read-write
Network access (outbound) com.apple.security.network.client
Network access (inbound, e.g. a local server) com.apple.security.network.server
Camera com.apple.security.device.camera
Microphone com.apple.security.device.microphone
USB devices com.apple.security.device.usb

If the app manages files outside the container (a text editor, a media organizer), you need to use security-scoped bookmarks or NSOpenPanel to gain access. Users pick files through the standard open dialog, you save a bookmark, and restore access on subsequent launches.

App Review checks that sandbox entitlements match the app’s described functionality. Declaring network server access for a note-taking app that does not need it can raise review questions or lead to rejection.

Updates

The App Store handles updates automatically. Users get notified, the store downloads the update, and the system installs it. No work on your side beyond submitting a new version.

Pricing

Under Apple’s standard terms, the commission is 30% for paid apps and in-app purchases. It drops to 15% for auto-renewable subscriptions after the first year and for developers enrolled in the App Store Small Business Program, which has a $1M annual proceeds threshold across associated accounts.

Pros

  • Automatic updates with no infrastructure.
  • Built-in payment processing and purchase validation.
  • Discoverability through the Mac App Store search and categories.
  • Users trust the source.

Cons

  • Sandboxing restricts some workflows, including broad file system access, process interaction, and some automation or accessibility use cases.
  • App review adds delays and uncertainty.
  • 15-30% revenue share.
  • No trial or demo support without building your own.

Notarized direct download

Distributing outside the App Store — through your own website or GitHub Releases, often with an updater such as Sparkle — is the most flexible option.

Requirements

  • Apple Developer Program membership ($99/year) for notarization.
  • Code signing for every distributed executable with a Developer ID certificate (not an App Store certificate — they are different).
  • Notarization by Apple’s automated service. Apple scans the binary for malware, checks code signing, and issues a ticket that Gatekeeper can find online or in the stapled app.
  • Hardened Runtime enabled. This adds security protections without sandboxing the app.

Hardened Runtime vs Sandbox

Hardened Runtime is not a sandbox. It is a set of security protections. Some entitlements allow exceptions to those protections, while others grant access to protected resources such as the camera or microphone:

Capability Entitlement
Allow JIT compilation com.apple.security.cs.allow-jit
Allow unsigned executable memory com.apple.security.cs.allow-unsigned-executable-memory
Allow DYLD environment variables com.apple.security.cs.allow-dyld-environment-variables
Camera com.apple.security.device.camera
Audio input com.apple.security.device.audio-input

Unlike sandboxing, Hardened Runtime does not restrict file system access by default — subject to macOS permissions, the app can read and write anywhere the user can. Some entitlements allow hardening exceptions; others grant access to protected resources.

Notarization process

# 0. Store notarization credentials once
xcrun notarytool store-credentials "AC_PASSWORD" \
  --apple-id "you@example.com" \
  --team-id "TEAMID"

# 1. After exporting and signing MyApp.app, package it
ditto -c -k --keepParent MyApp.app MyApp.zip

# 2. Upload for notarization
xcrun notarytool submit MyApp.zip \
  --keychain-profile "AC_PASSWORD" \
  --wait

# 3. Staple the ticket to the app
xcrun stapler staple MyApp.app

# 4. Recreate the ZIP so it contains the stapled app
ditto -c -k --keepParent MyApp.app MyApp.zip

The --wait flag blocks until Apple finishes scanning. For CI, use polling instead.

Updates with Sparkle

Without the App Store, you need an update mechanism. Sparkle is the de facto standard. It is an open-source framework that checks for updates, downloads them, and applies them.

Sparkle requires:

  • An appcast XML file hosted on your server (or GitHub Pages).
  • An EdDSA key pair for signing updates.
  • The framework embedded in the app.
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
  <channel>
    <item>
      <title>Version 2.0</title>
      <sparkle:version>2.0</sparkle:version>
      <enclosure url="https://example.com/MyApp-2.0.zip"
                 sparkle:edSignature="..."
                 length="1234567"
                 type="application/octet-stream" />
      <sparkle:releaseNotesLink>
        https://example.com/release-notes-2.0.html
      </sparkle:releaseNotesLink>
    </item>
  </channel>
</rss>

Sparkle handles differential updates, background downloading, and installation. It is more work than the App Store’s built-in updates but gives you full control.

Pros

  • No App Sandbox restrictions. Other macOS permissions and security controls still apply.
  • No app review. Ship when you want.
  • Full control over pricing, trials, and licensing.
  • No revenue share.
  • Users can download from your site directly.

Cons

  • You own distribution and bandwidth. For large apps, hosting costs add up.
  • Update infrastructure requires setup (appcast, signing, server).
  • Notarization can fail with cryptic errors (especially for apps with embedded interpreters or JIT).
  • Users may see Gatekeeper warnings if notarization or signing is misconfigured.

Homebrew

Homebrew is a package manager for command-line tools and GUI apps that appeals to developers and power users. For GUI apps, a cask usually installs the same signed and notarized artifact offered as a direct download.

Requirements

  • A cask or formula definition, typically submitted to an official Homebrew repository or published in your own tap.
  • No Apple Developer Program membership required (for command-line tools — GUI apps still need signing and notarization to avoid Gatekeeper warnings).
  • The definition describes how to download, verify, and install the software.

Cask and formula structure

cask "myapp" do
  version "2.0"
  sha256 "abc123..."

  url "https://example.com/MyApp-#{version}.zip"
  name "MyApp"
  desc "A useful macOS app"
  homepage "https://example.com"

  livecheck do
    url :homepage
    strategy :page_match
    regex(/href=.*?MyApp[._-]v?(\d+(?:\.\d+)+)\.zip/i)
  end

  depends_on macos: ">= :ventura"

  app "MyApp.app"

  zap trash: [
    "~/Library/Application Support/MyApp",
    "~/Library/Preferences/com.example.MyApp.plist",
  ]
end

For open-source command-line tools that Homebrew can build from source, use a formula:

class MyTool < Formula
  desc "A useful CLI tool"
  homepage "https://example.com"
  url "https://example.com/mytool-2.0.tar.gz"
  sha256 "def456..."
  license "MIT"

  def install
    system "make", "install", "PREFIX=#{prefix}"
  end

  test do
    system "#{bin}/mytool", "--version"
  end
end

Binary-only command-line tools may need a cask or a personal tap instead of a formula in homebrew/core.

The workflow

  1. Write the cask and submit a PR to homebrew/homebrew-cask (or write a formula and submit it to homebrew/core for a suitable CLI tool).
  2. Homebrew maintainers review the definition for correctness, security, and standards.
  3. Once merged, users can install with brew install --cask myapp.
  4. Updates require the definition to receive the new URL and checksum, either manually or through automation.

Auto-updating definitions

New formulas and casks in Homebrew’s official repositories are included in autobump automation by default. A livecheck block helps Homebrew discover new upstream versions when the default URL-based detection is insufficient. Definitions excluded from autobumping need manual update PRs.

Pros

  • Reaches developers directly — your target audience for developer tools.
  • No package repository infrastructure to manage. Homebrew hosts the definition, not the binary (you still host the download).
  • No revenue share.
  • No sandbox restrictions (definitions distribute whatever you build).

Cons

  • Not for general consumers. Most Mac users do not use Homebrew.
  • The review process for new casks can take days or weeks.
  • Updates rely on the definition being current. If automation cannot update it and you forget to do so, users get stale versions.
  • For apps without their own updater, users need to run brew upgrade.

Comparison table

Mac App Store Direct + Notarization Homebrew
Developer fee $99/year $99/year None (but you still need $99/year for code signing GUI apps)
Sandbox required Yes No No
App review Yes No (notarization only) Cask or formula review
Revenue share 15-30% None None
Updates Automatic App-managed (for example, Sparkle) brew upgrade or the app’s updater
User base General Mac users General Mac users Developers/power users
Bandwidth Apple hosts You host You host
Trial/demo support No built-in Yes Depends on the distributed app
Distribution control Apple You You

A pragmatic approach

Many macOS developers use a multi-channel strategy:

  • App Store for discoverability and users who prefer it.
  • Direct download from the website for users who want the latest version, trials, or features that are incompatible with App Sandbox.
  • Homebrew for developer-oriented tools.

App Store distribution requires a sandboxed build signed for the store. Direct downloads require a Developer ID-signed build and usually need an update mechanism such as Sparkle. A Homebrew cask can install the same signed and notarized artifact that you distribute directly.

Building for multiple channels

Xcode supports multiple build configurations per target. Set up:

  • Release — for the App Store (sandbox enabled, App Store certificate).
  • Release-Direct — for direct distribution (no sandbox, Developer ID certificate).

The CODE_SIGN_IDENTITY differs per configuration. The App Store build uses sandbox entitlements. If the direct build needs Hardened Runtime exceptions or protected-resource permissions, keep them in a separate file:

  • App.entitlements — includes sandbox entitlements.
  • App-Direct.entitlements — includes only the Hardened Runtime exceptions or protected-resource permissions that the direct build needs.

Signing helpers

For a simple app bundle, the signing flow looks like this. If your app embeds frameworks, helpers, or extensions, sign those nested components first and sign the outer app last.

# Sign and package for direct distribution
# Add --entitlements App-Direct.entitlements if the direct build needs it.
codesign --force --options runtime --timestamp \
  --sign "Developer ID Application: Your Name (TEAMID)" \
  MyApp.app
ditto -c -k --keepParent MyApp.app MyApp.zip

# Submit for notarization
xcrun notarytool submit MyApp.zip \
  --keychain-profile "AC_PASSWORD" \
  --wait

# Staple
xcrun stapler staple MyApp.app

# Recreate the ZIP so it contains the stapled app
ditto -c -k --keepParent MyApp.app MyApp.zip