macOS Notarization and Stapling - Ninety-Minute Pass for Unity and Godot Steam Mac Builds 2026

If you ship PC-first but still support Mac, you have probably watched this movie. The Linux build is fine. The Windows zip is boring but predictable. Then someone on the team opens the Mac build from Discord, gets a blunt "cannot be opened because the developer cannot be verified" dialog, and suddenly your "we support Mac" claim turns into a support thread about right-click Open acrobatics.
That is not a player education problem. It is a distribution hygiene problem. In 2026, Apple still expects consumer-downloaded software outside the Mac App Store to pass through notarization when you want predictable behavior under Gatekeeper and current security tooling. Steam does not magically baptize your binary. If you want your Steam depot discipline to extend cleanly to macOS, you need a repeatable signing lane that ends with a stapled ticket, not a prayer.
This article is a ninety-minute operations pass you can run before every Mac candidate upload. It is engine-agnostic in spirit but Unity and Godot specific where export settings matter. It assumes you are willing to use Xcode command-line tools and Apple’s notarytool on a maintainer Mac, not that you enjoy reading PKCS12 error strings for fun.
Why this matters now
Three pressures stack in 2026 for small teams shipping Mac builds alongside Windows.
First, player expectations hardened. gatekeeper warnings are screenshot-friendly. Players interpret them as malware signals even when you are clean. That costs wishlists, refunds, and review tone.
Second, toolchain drift is real. Apple iterates signing expectations, Xcode CLT updates move behaviors, and silent differences between "signed" and "notarized + stapled" show up under quarantine attributes (com.apple.quarantine) when files arrive through browsers, launchers, or zip unpackers. Your internal copy from a network share is not the same test as a fresh download.
Third, Mac support is bundled into cross-platform credibility for indie Steam releases. If you already invested in Game Porting Toolkit and Mac-readiness thinking, notarization is the boring sequel that actually ships.
Fourth, reviewer bundles hardened into frozen tuples through May 2026 submission windows. Partner and certification audiences increasingly reject hand-wavy we signed it somewhere stories when replay drills compare artifacts. Your Mac lane should capture notarytool submission identifiers, Accepted status references, and stapler validate output beside Windows SHA rows and privacy disclosure revisions inside one release-candidate packet revision—same discipline you apply when aligning SLSA-style attestation snapshots with CI promotions. If lane-specific SLA splits steady-week versus peak-week intake load for governance reviews, keep backlog sequencing explicit using references such as the Quest OpenXR governance evidence SLA resource list so Mac signing evidence does not stall escalation queues behind unrelated backlog spikes.
Beginner quick start
If you only remember what to do, remember this sequence:
- Build a release
.appwith hardened runtime compatible settings. - Code sign the bundle and every nested code object your pipeline produces.
- Zip the app the way notarization expects, submit with notarytool, wait for Accepted.
- Staple the ticket to the app.
- Re-zip (or repackage) for Steam with a naming convention that matches your build identity checklist.
If any step is unfamiliar, the sections below unpack the decisions without assuming you are a PKI engineer.
Direct answer
Notarization is Apple’s automated scan plus ticket issuance for software signed with a Developer ID certificate. Stapling attaches that ticket to your deliverable so offline validation is smoother. For Steam-style downloads, you want Accepted status, a stapled app, and proof from stapler validate before you call the Mac build ready.
Who this is for and how long this takes
This pass targets:
- Solo devs who wear release engineering on shipping week
- Small teams where one person owns "the Mac laptop"
- Unity or Godot pipelines that already produce a Mac
.appbut skip Apple paperwork
Time: about ninety minutes end-to-end the first time you establish credentials and scripts, then often twenty to forty minutes per candidate once stable. Add buffer if you are installing Xcode CLT or renewing certificates.
Shared vocabulary (read this once)
Developer ID Application certificate
Your identity for signing software distributed outside the Mac App Store. Not the same as an Apple Development cert used for local debugging.
Developer ID Installer certificate
Used when you sign installer packages (.pkg). Games often ship .app in a zip or dmg. Know which path you use.
Entitlements
Plist-declared capabilities and hardening settings. Mismatched entitlements are a classic reason notarization fails with cryptic log output.
Hardened Runtime
Apple’s stricter execution model for signed code. Games frequently need explicit entitlements for JIT, unsigned memory, or debugging-adjacent features. Getting this wrong shows up late.
notarytool
CLI shipped with modern Xcode toolchains for submitting and polling notarization. Older altool paths are not where you want to live in 2026 documentation.
Preconditions checklist (do not skip)
Before you burn time:
- A paid Apple Developer Program membership in good standing.
- Access to Certificates, Identifiers & Profiles on developer.apple.com.
- A Mac with Xcode Command Line Tools installed (
xcode-select --install). - A known-good release build configuration. Debug Mono toggles and plugin experiments belong in a different lane.
- A single owner for secrets: p12 export discipline, CI token handling, and App Store Connect API key if you automate notarytool.
If you parallelize signing across three laptops without a shared checklist, you will get three different Team IDs in your logs and a week of confusion.
Phase 1 - Identity and certificate sanity (minutes 0-15)
Lock your Team ID
Your Apple Team ID should appear consistently in:
- Keychain certificate names
codesignoutput (codesign -dv --verbose=4 Your.app)- notarytool submission metadata
Write it in your runbook. If you have multiple Apple accounts on one machine, specify identity strings explicitly in scripts.
Prove the right certificate exists
In Keychain Access, verify you have Developer ID Application (and Developer ID Installer if you ship .pkg). If you are still signing with Mac Developer, you are in the wrong movie for public Steam drops.
Export discipline
If you must move signing material to a build agent:
- Prefer hardware-bound or scoped secrets over emailing p12 files.
- Rotate after contractor churn.
- Never commit certs to git, even private repos—you already knew that, but midnight release brains forget.
Phase 2 - Unity Mac export checklist (minutes 15-40)
Unity teams should treat Mac like a first-class release tuple, not a toggled afterthought. Your goals:
- Mono/IL2CPP choice documented per title (changing mid-patch changes support surface)
- Metal baselines consistent with your advertised min spec
- Plugins and native libraries either included and signed or removed—half-linked dylibs are a signing nightmare
Project settings that frequently bite
- Scripting backend and API compatibility choices that alter native code layout
- Incremental GC and thread models that shift crash signatures (not a signing issue, but it changes what you defend in triage)
- Mac Player settings for created with hardened runtime style requirements—treat Apple guidance as the source of truth for your Unity version
Signing from Unity versus post-processing
Some studios sign inside the editor pipeline, others export unsigned then sign with codesign in a script. Both work if you are consistent. What fails is double signing with conflicting flags or signing before the final bundle layout exists.
Nested binaries
Games drag along:
- native plugins
- burst-compiled shards
- third-party analytics SDKs
Each may be its own code object. Your ninety-minute pass includes a recursive signing validation habit: if codesign verification fails on a nested framework, fix there before notarization.
A practical signing order pattern
When you sign outside the editor, teams converge on the same boring recipe:
- Sign deepest nested libraries first (dylibs inside
.frameworkbundles, then the framework, then the app shell). - Re-run deep verification after every plugin vendor drops a hotfix.
- Keep a single canonical export folder path so scripts do not pick up stale
YourGame (1).appcopies.
If your pipeline emits a .app and a folder of loose debug tools, strip the tools before signing or sign them as separate deliverables with their own identities. Mixing developer utilities inside the player bundle is how you accidentally ship a curl binary that breaks notarization heuristics.
Unity Player build artifact checklist
Before you call codesign, do a ten-minute human scan:
MacOSexecutable present and executable bit saneResourcesandDatafolders match the build stamp you think you shipped- no accidental inclusion of MonoBleedingEdge dev folders from wrong export profile
- Steam API dylib and overlay expectations documented (presence/absence is fine—lies are not)
Write those checks into the same spreadsheet row you use for Windows hash confirmation. Release owners should not ask "which Mac zip" at upload time.
Phase 3 - Godot Mac export checklist (minutes 15-40, parallel conceptually)
Godot releases require similar discipline with different UI:
- Export templates aligned to your exact engine version
- Identifiers (bundle id) consistent with your franchise naming
- Entitlement needs surfaced early if you use GPU, network, or sandbox-adjacent stacks
Godot and command-line packaging
Many Godot teams build .app through the export wizard then run the same codesign and notarytool sequence as Unity teams. Do not assume the exported .app is "already fine" because it launches from a trusted folder.
Version locks that matter
Godot patches can change export output layout subtly. Treat patch upgrades as a reason to re-run:
- deep
codesignverify - a short notary fire-drill on a throwaway branch if Apple tooling also moved
Teams that skip this get surprised when the only change was "we bumped Godot for a renderer fix" and suddenly a nested library path shifted.
Suggested ninety-minute agenda (meeting-safe)
If you are running this pass in a live call, use a visible timer:
| Minutes | Focus | Done means |
|---|---|---|
| 0-10 | Team ID and certs | Correct Developer ID present; no ambiguous identities |
| 10-35 | Export release .app |
Fresh tuple documented; obvious junk removed |
| 35-50 | codesign + deep verify | codesign --verify --deep --strict clean |
| 50-70 | zip + notarytool | Submission id captured; status Accepted |
| 70-85 | stapler + validate | Staple succeeded; validation output saved |
| 85-90 | Steam repackage + hash | Mac artifact row matches upload checklist |
When you finish early, spend the surplus on quarantine rehearsal, not on rerunning unknown steps twice without logs.
Phase 4 - codesign rituals that survive review (minutes 40-55)
Verify before you zip
Use deep verification on the .app:
codesign --verify --deep --strict --verbose=2 Your.app
If --deep complains, read the nested path and fix signing order. Guessing slows you down more than reading logs.
Entitlements file hygiene
Maintain a versioned entitlements plist in your repo—yes, that is one small file that saves ten hours. Changes should be diff-reviewed like shader includes. If you add a feature that needs JIT or allow-unsigned-executable-memory, you should be able to point to the entitlement line and the product reason.
Timestamp and chain options
Follow current Apple guidance for timestamp servers and signing flags for your toolchain year. The point is reproducibility: your codesign invocation should live in a script referenced from release notes, not in muscle memory.
Phase 5 - notarytool submission (minutes 55-75)
Package for upload
Notarization wants a readable archive format—commonly zip for an app bundle. The key is producing exactly what your runbook says. Never submit a .app that still references broken symlinks or debug plugins you thought you deleted.
Hygiene traps in the zip step
- Remove
.DS_Storeclutter where feasible; it is not your primary failure mode but it annoys reproducible archives. - Ensure the zip contains one top-level
.appwith the expected name—double-nesting wastes review time. - If you compress on Windows by mistake, revisit permission bits; permission drift is a silent macOS footgun.
Submit and poll
With notarytool you typically:
- authenticate through stored credentials or App Store Connect API key workflow
- submit the zip with a clear
--waitor a poll loop if your CI cannot block - capture logs on rejection—do not rerun blindly
Apple’s rejection logs are dense but structured. Search for the first error line, not the hundredth warning noise.
Reading rejection logs without theatrics
When notarization fails, export the JSON log Apple provides and skim in this order:
- status summary block
- first issues entry with severity you cannot explain away
- pathways mentioning codesign, hardened runtime, or entitlements
Teams that panic-scroll miss the single line that says which nested binary violated rules. Copy that path into your issue tracker before you try random entitlement tweaks.
Rate limits and human pacing
You usually do not spam notarization like unit tests. If you are iterating fast, batch fixes and resubmit with intention. Your release owner should know how many submits happened tonight and why each one differed.
Phase 6 - stapling and local proof (minutes 75-85)
After Accepted:
- Run stapler on the
.appper Apple docs. - Stapling must succeed before you repackage for Steam.
- Validate:
stapler validate -v Your.appshould reflect a healthy ticket story.
If stapling fails, treat it as release-blocking for Mac. Players should not be your first line of stapler debugging.
Phase 7 - Steam packaging and download realism (minutes 85-90)
Steam’s Mac path adds operational constraints:
- Your depot should carry the same binary you validated, including resource forks quirks if you still hit legacy asset layouts.
- Compress deterministically. Track hashes alongside Windows builds in your release-day packet habits.
Depots, branches, and default Mac visibility
Treat your Mac depot like any other first-class lane:
- Name branches so support can tell whether a player is on hotfix versus default Mac without guessing.
- Keep patch notes explicit when Mac-only fixes ship; silence creates refund risk.
- If you defer Mac updates behind Windows, publish that policy. Surprises are what generate negative reviews.
Hash discipline across OSes
Many teams already compute SHA-256 for Windows. Add a parallel row for the stapled .app or final Mac container. When a producer asks "is this the build we signed?", the answer should be a hash match, not a Slack thumbs-up.
Quarantine rehearsal
Download your build like a player:
- through a browser or a test folder receiving a copied zip
- extract, run, observe Gatekeeper behavior
Do not only launch from /Applications after Xcode touched the files. That masks real player experience.
CI without fairy tales
Teams differ wildly here. Some run signing on a single Release Mac Mini; some use hosted macOS. The honest baseline:
- If you automate, centralize identity strings, keychain unlocked state, and notarytool credentials.
- Emit artifacts:
codesignlogs, notary submission IDs, stapler validation outputs. Attach them to internal release notes.
If you are not ready for automation, a documented manual run is still better than "Dave did something on his laptop once."
Common mistakes (learn from strangers on forums)
- Signing with the wrong certificate and discovering it only on another machine’s Keychain.
- Partial signing of nested frameworks after a plugin update—always re-verify deep.
- Entitlement drift when upgrading Unity/Godot minors; treat engine bumps as re-validate signing events.
- Submitting the wrong zip—debug app, wrong branch artifact, or stale symbols folder included accidentally.
- Skipping stapling because notarization Accepted felt "good enough."
- Testing only from/Xcode-cleansed paths, missing quarantine behavior players hit day one.
Security and privacy adjacency
macOS security posture intersects store ecosystems beyond Steam. If you also ship on iOS, your Apple workflow discipline pays double. A adjacent internal workflow that pairs well is App Store Connect App Privacy inventory discipline—not because Steam wants the same forms, but because your telemetry honesty should be consistent across Apple targets.
Binary size and packaging choices also interact with signing time and user download pain. If you are shaving delivery weight, coordinate with install-size passes so you do not fight compression twice.
Key takeaways
- Treat notarization + stapling as part of done for Mac, not an optional Apple curiosity.
- Use Developer ID identity consistently and pin your Team ID in runbooks.
- Deep-verify
codesignbefore zip; fix nested frameworks first. - Maintain a versioned entitlements plist; justify any hardened-runtime relaxation with product reasons.
- Submit with notarytool, archive submission IDs, and read structured rejection logs carefully.
- Staple then
stapler validatebefore Steam packaging. - Rehearse quarantine behavior on a downloaded copy, not only local dev paths.
- Pair Mac distribution hygiene with depot identity and release packet discipline you already use on PC.
FAQ
Do players still need to right-click Open if we notarize?
Notarization and stapling are the official path to avoiding routine Gatekeeper dead-ends for downloaded software. If you still see blocks, you usually have partial signing, staple failures, or players receiving unsigned sidecar binaries.
Is a DMG better than zip for Steam?
Depends on your game’s expectations. Many Steam Mac titles ship zips or nested structures. Pick one approach per title, document it, and validate signing on the final container players receive.
What about Apple Silicon versus Intel slices?
Your export settings must produce the architecture slice you advertise. Universal builds add complexity but reduce support tickets. Whatever you pick, reflect it in Steam hardware labels and internal QA matrices.
Can Godot games skip notarization if they are open source?
Distribution realism beats philosophy. Your players download a signed artifact. Gatekeeper evaluates that artifact. Open source does not grant a bypass.
What if we only distribute through Steam and never via the browser?
Some workflows reduce quarantine exposure, but you should still treat notarized + stapled as your baseline done definition. Support incidents arrive from sideloads, journalists, and creator copies that do not follow the happy path.
Does notarization replace malware scanning in CI?
No. It is Apple’s pipeline, not yours. Keep whatever static and dynamic checks you already believe in for third-party native code—especially analytics and anti-cheat SDKs.
How does this relate to the Game Porting Toolkit story?
GPTK and related compatibility layers influence feasibility and performance investigations. Notarization is still about your exported deliverable identity. Read both topics, but do not merge them into one vague "Mac stuff" task.
Outbound references (verify against current Apple docs)
Use Apple’s official documentation for notarytool, hardened runtime, entitlements, and stapler as your primary references. Third-party summaries go stale quickly; Apple updates the happy path as Xcode evolves.
Conclusion
macOS support is not a generosity badge. It is a support-cost choice. If you choose it, choose the boring excellence path: repeatable signing, logged notarization, stapled tickets, and download-realistic QA. Your ninety-minute pass is cheap compared to a launch-week forum firefighting Gatekeeper screenshots at 2am.
If you want your Mac lane to feel as disciplined as your OpenXR and PC rollout workflows, treat this checklist like a release gate—same owner, same evidence rows, same refusal to shrug at yellow warnings that your players will turn red. Before signoff, confirm your reviewer-facing revision explicitly binds Apple receipts and staple validation transcripts to the promoted Steam depot artifact digest row so governance replay cannot confuse an intermediate zip with the stapled drop players receive.