ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-05-02Advanced

Shipping Tauri 2 Desktop Apps to Production with Antigravity — Code Signing, Auto-Updates, License Verification

After tauri build succeeds, the real work begins: notarization, EV signing, your own update server, license verification, and Stripe-driven monetization. A field guide built from running paid Tauri 2 apps in production with Antigravity.

antigravity435tauridesktop2code-signingnotarization2monetization31license

Premium Article

Shipping Tauri 2 Desktop Apps to Production with Antigravity

Most developers who finish a Tauri 2 app discover the painful truth on release day: the hard part wasn't writing the code. It was everything that comes after tauri build returns successfully. Notarization rejections, SmartScreen warnings, broken updaters, license keys that don't survive offline use, Stripe webhooks firing twice — these are the topics that the official quickstart politely sidesteps, and they're exactly where every paid desktop app gets stuck.

I've shipped iPhone and Android apps as a solo developer for years, but desktop is a different animal. The store no longer handles distribution for you. You build the pipeline yourself, or you don't ship. This article walks through the full production deployment pattern I use for paid Tauri 2 apps, with Antigravity's agents doing the heavy lifting on the boilerplate so I can focus on the design decisions.

Why "Production Tauri 2" is Genuinely Hard

Tauri's edge over Electron isn't just smaller binaries and faster startup. It's distribution freedom. Tauri 2 generates native installers directly and lets you bring your own update infrastructure. That freedom is powerful — and it means everything is your responsibility.

In the Electron world, electron-updater and electron-builder automate most of this. Tauri 2 ships with tauri-plugin-updater, but key management, distribution endpoints, and staged rollout strategy are yours to build. The reason Antigravity matters here is that you can split this work across agents naturally: the Manager Surface lets one agent write the Rust signature-verification code while another writes the TypeScript license UI, and a third writes your Cloudflare Worker. That separation of concerns is the design philosophy that runs through this entire article.

A second reality: the failure modes of desktop distribution are completely different from web. A bad deploy on the web means rolling back in seconds; a bad release of a desktop app sits on customer machines until they relaunch. That changes how you design every layer. Your update server has to be conservative by default. Your license format has to survive across versions. Your installer has to be re-runnable without breaking existing data. None of these are obvious until your first hot-fix that has to ship without breaking anyone, and you discover the file you needed to update is owned by an installer that overwrites it.

The third thing that catches people: Tauri 2's smaller surface area cuts both ways. Yes, the binary is small and starts fast. But the ecosystem is younger than Electron's, which means many problems you'll hit don't have a Stack Overflow answer. This is exactly where Antigravity earns its keep — its agents can read the Tauri 2 source directly, propose fixes that match upstream's intent, and explain why a particular API exists rather than guessing at usage from forum posts. I've spent more than one evening watching the Manager Surface track down an issue across tauri-plugin-updater's Rust code, the Tauri JS bindings, and the underlying WebView crate — work that would have taken me a full day alone.

If you're migrating from Tauri 1.x or just getting started, read the Antigravity × Tauri Desktop App Development Guide first — this article picks up where that one ends.

Automating macOS Notarization with Antigravity

Code signing alone isn't enough on macOS. A signed .dmg or .app will still display "cannot verify the developer" until you submit it to Apple for notarization, then staple the result to the binary. Skip notarization and your users see a scary warning — and Gatekeeper will block the app entirely on certain configurations.

I run the entire pipeline through one script. When I asked Antigravity to "write notarize.sh that runs after tauri build, fails the GitHub Actions job on rejection, and posts the error log to Slack," it produced something close to this on the first pass:

#!/usr/bin/env bash
# notarize.sh - Tauri 2 macOS notarization pipeline
# Required env: APPLE_ID, APPLE_TEAM_ID, APPLE_APP_PASSWORD, SIGNING_IDENTITY
set -euo pipefail
 
APP_PATH="src-tauri/target/release/bundle/macos/MyApp.app"
DMG_PATH="src-tauri/target/release/bundle/dmg/MyApp_1.0.0_universal.dmg"
 
# 1. Sign the .app — --options runtime is mandatory or notarization will reject
codesign --force --deep --options runtime \
  --sign "$SIGNING_IDENTITY" \
  --entitlements src-tauri/entitlements.plist \
  "$APP_PATH"
 
# 2. Zip the bundle (notarization only accepts ZIP/DMG/PKG)
ditto -c -k --keepParent "$APP_PATH" "MyApp.zip"
 
# 3. Submit synchronously and wait for Apple's verdict
xcrun notarytool submit "MyApp.zip" \
  --apple-id "$APPLE_ID" \
  --team-id "$APPLE_TEAM_ID" \
  --password "$APPLE_APP_PASSWORD" \
  --wait \
  --output-format json > notarize_result.json || {
    SUBMISSION_ID=$(jq -r '.id' notarize_result.json 2>/dev/null || echo "unknown")
    echo "❌ Notarization failed: $SUBMISSION_ID"
    xcrun notarytool log "$SUBMISSION_ID" \
      --apple-id "$APPLE_ID" \
      --team-id "$APPLE_TEAM_ID" \
      --password "$APPLE_APP_PASSWORD" || true
    exit 1
  }
 
# 4. Staple the ticket to both the .app and the DMG
xcrun stapler staple "$APP_PATH"
xcrun stapler staple "$DMG_PATH"
 
# 5. Verify Gatekeeper accepts the result
spctl --assess --type execute --verbose=4 "$APP_PATH"
echo "✅ Notarized: $DMG_PATH"

The two non-obvious requirements are --options runtime and the staple step. Forget the runtime flag and Apple rejects with "Hardened Runtime is not enabled," buried in the log. Forget stapling and offline users get blocked because Gatekeeper can't reach Apple's servers to verify. Both errors look like generic failures at first glance, so I have a Sub-Agent dedicated to parsing notarytool's JSON output and categorizing rejection reasons. That alone saves hours.

The expected output is accepted from notarytool, followed by spctl reporting source=Notarized Developer ID. If you see Unnotarized Developer ID, stapling failed — usually because the script tried to staple before notarization was actually complete.

A subtle thing about notarytool's behavior worth knowing: the --wait flag will sit and poll for up to about 15 minutes per submission. If your CI runner has a tight timeout, you may need to either increase the runner timeout or split into a non-blocking submit followed by a separate poll job. I went with the simpler --wait approach because every minute of CI complexity I added paid for itself only in marginal speed improvements, while the simpler version stayed reliable across hundreds of releases. Pick the option that minimizes the moving parts in your pipeline.

There's also a question of what to do when notarization is slow but ultimately succeeds. Apple's median turnaround is under three minutes, but I've seen 12-minute submissions during product launches and right after macOS dot releases ship. If you're publishing on a schedule (e.g. weekly Friday releases), staggering your notarization submissions away from common rush hours helps. The asymmetry: notarization has no SLA, so when Apple is slow, you wait. Build that variance into your release schedule rather than fighting it.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
You will get past the macOS notarization and Windows EV signing walls that block almost every Tauri 2 release, with reusable scripts you can drop into any pipeline today
You can stand up a Cloudflare Workers + R2 update server with staged rollout, rollback, and signature verification — production-grade, not a toy
You will be able to design a Stripe Checkout to offline-verifiable license flow that respects users while keeping casual piracy out of your revenue
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-06-30
The App Open Ad Antigravity Wrote for Me Fires Every Time I Return From My Own Paywall or Rewarded Video
Ask Antigravity to add an App Open ad and it shows one the instant you return from your own rewarded video or the Google Play purchase sheet — which also brushes against AdMob policy. Here is a foreground arbiter that records why the app came back, with working Kotlin and a verification matrix.
App Dev2026-06-25
An Agent Granted 'Watch an Ad to Unlock a Wallpaper' Entirely Client-Side — Re-Verifying Reward Grants with AdMob SSV
I asked an Antigravity agent to wire up 'watch a rewarded ad to unlock a wallpaper,' and it returned an implementation that wrote the unlock flag client-side only. Here is why that is not enough, how I re-verified the reward grant with AdMob server-side verification (SSV), and how I stopped double grants too.
App Dev2026-06-19
I Started the Ad SDK Before Asking for ATT — the Init-Order Bug That Quietly Lowered First-Session eCPM
When I rolled AdMob mediation out to four iOS apps, only the very first session showed weaker ad revenue. The cause was the order between the ATT prompt and MobileAds initialization. Here is why the order matters, plus how I had Antigravity audit the init sequence across all four apps.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →