DEV Community

Devanshu Verma
Devanshu Verma

Posted on

Shipping a Mac app without a $99 Apple Developer account

I build Kopi, a free, open-source (MIT) clipboard manager for macOS and Windows. It's written in Rust with Tauri 2 and React, and it's in alpha.

Kopi's clipboard panel on macOS

This post is about one decision that took more thought than any feature: how to ship a Mac app without paying Apple $99 a year. I wrote it up as a decision record in the repo (ADR 0006), and this is the longer story behind it.

The problem: $99 a year to open without a warning

On macOS, an app downloaded from the internet opens without a Gatekeeper warning only if it is signed with an Apple Developer ID certificate and notarized by Apple. Getting that certificate means joining the Apple Developer Program, which costs $99 a year.

For a commercial app that's a normal cost of doing business. Kopi is free and open source, and I decided it won't pay that fee every year. So the question became: what's the best Mac experience I can ship without it?

Why "just don't sign it" doesn't work

My first thought was the obvious one. Skip the paid certificate, sign the app ad-hoc (which is what you get by default when you build locally), and tell people to click through the warning.

That falls apart because of what Kopi does. A clipboard manager needs two macOS permissions:

  • Accessibility, so it can paste into the app you're using.
  • Pasteboard access ("Paste from Other Apps"), so it can see what you copy.

macOS doesn't remember these grants by app name. It ties them to the app's designated requirement, a rule baked into the code signature that says "this is the same app." For an ad-hoc signature, that requirement includes the build's own hash. Every new build has a new hash, so every new build looks like a different app to macOS.

In practice that meant my alpha testers lost both permissions on every update. Paste stopped working, history stopped recording, and they had to dig into System Settings and re-grant everything. For an app that ships frequent alpha updates, that's not something I could ask people to put up with.

The fix: a self-signed certificate that never changes

The key insight is that the designated requirement doesn't have to mention the build's hash. If you sign with a real certificate, even a self-signed one, the requirement can be based on the bundle identifier and the certificate instead.

So I created a self-signed code-signing certificate and stored it in the repository's secrets. Every release is signed with it. The resulting designated requirement looks like this:

identifier "in.devanshuverma.kopi" and certificate leaf = H"…"
Enter fullscreen mode Exit fullscreen mode

That's the bundle identifier plus the hash of the certificate's leaf. Neither changes between builds. To macOS, version N+1 is the same app as version N, so the Accessibility and pasteboard grants carry over.

This doesn't make Gatekeeper happy. A self-signed certificate isn't trusted by Apple, so the app is still blocked on first open. But it solves the much worse problem of permissions resetting on every update.

You can check this yourself on any signed app:

codesign -d -r- /Applications/Kopi.app
Enter fullscreen mode Exit fullscreen mode

One snag with Tauri

Tauri's bundler can sign macOS builds for you if you hand it a certificate through the APPLE_CERTIFICATE environment variable. The catch: its import only finds Apple-issued certificates, and it refuses to build a release without one. A self-signed certificate isn't Apple-issued, so that path doesn't work.

The workaround is to have the release workflow import the certificate into the keychain itself, then let the build sign with that identity.

Accepting the first-open block, and making it painless

With a self-signed certificate, Gatekeeper will block Kopi the first time someone opens it. I decided to accept that and put the effort into making the first launch as painless as I could, in three ways.

An install guide. INSTALL_ALPHA.md walks through it step by step. On macOS 15 Sequoia and later, you allow Kopi in System Settings → Privacy & Security. On macOS 14 Sonoma, you Control-click the app and choose Open. The guide also explains each permission and what happens if you don't grant it.

A .dmg background that shows the steps. Most people never read install docs, but everyone sees the disk image window when they drag the app to Applications. So the .dmg background itself shows what to do after the first block.

A one-line Terminal install. For people comfortable with Terminal, there's a script:

curl -fsSL https://kopi.devanshuverma.in/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

It downloads Kopi, verifies the download, and copies the app to Applications. The script doesn't quarantine the app, so Gatekeeper doesn't block the first launch.

Piping a script into bash from the internet deserves some care, so the script has a guardrail: it only installs a Kopi that is signed with Kopi's certificate. If the signature doesn't match, it doesn't install. As always, you can read the script before running it.

Updates: blocked once, never again

The first install is the only time Gatekeeper gets in the way. After that, Kopi updates itself with the Tauri updater.

The updater signs updates with its own key, separate from the code-signing certificate. It installs updates without quarantine, so Gatekeeper doesn't block them. And because every build carries the same designated requirement, the permissions survive too.

So the full experience for a tester is: one "Open Anyway" on day one, grant two permissions, and then updates just arrive. Kopi checks every few hours and installs in the background, and you can turn automatic updates off in Settings.

The options I rejected

Here's what else I considered and why I didn't go with it:

Option Why not
Developer ID + notarization Removes the first-launch warning, but costs $99 every year.
Ad-hoc signing only Free, but Accessibility and pasteboard permissions reset on every update.
Mac App Store Needs the same paid program, and Kopi can't run in the App Sandbox.
Homebrew cask The official tap disabled casks that fail Gatekeeper on 2026-09-01, and --no-quarantine is gone. A personal tap could work, but it would have to clear the quarantine flag itself. Deferred unless testers ask for it.

The honest trade-offs

This approach is not free of costs. They just aren't monetary.

Every new user has to find "Open Anyway". It lives in System Settings, and macOS 15 made it harder to get to than the old Control-click → Open trick. The install guide, the .dmg background and the Terminal script all exist to soften this, but it's still a step a notarized app wouldn't need.

Some people won't install an app macOS warns about. That's a reasonable instinct, and I'm not going to argue anyone out of it. Kopi being open source means you can read the code and build it yourself, but I accept that some people will close the warning and move on.

The certificate's private key has to be guarded. Anything signed with that key is something macOS will treat as Kopi, including inheriting the permissions people granted. That's why it lives in the repo's secrets. If it were ever lost, I'd have to create a new certificate, and every user would have to re-grant their permissions once.

For a free, alpha-stage app, I think that's the right trade. If Kopi grows to the point where the first-launch warning is the main thing holding people back, I'll revisit it. That's what the decision record is for.

Try Kopi

Kopi is open source under the MIT license and in alpha on macOS (14 Sonoma or later, Apple silicon or Intel) and Windows 10/11. It keeps your clipboard history locally with no account, filters out password-manager content, and has optional end-to-end encrypted sync over your local network.

If you try it, I'd love to hear what breaks. Issues on GitHub are the best place for bug reports, and Windows feedback is especially welcome right now.

Top comments (0)