App Store Release

Pre-flight, archive to TestFlight, App Privacy labels, and the release checklist.

"Deployment" for an iOS app means shipping to the App Store — there's no server to deploy, no Docker image, no environment to promote. This page is the end-to-end path from a local Xcode build to a build in front of reviewers: pre-flight the template's placeholders, archive and upload to TestFlight, declare App Privacy honestly, and work the release checklist.

Source: docs/app-store-release-overview.md in the template. Purchase validation against Apple's sandbox is covered separately in Purchase Testing; RevenueCat dashboard setup in RevenueCat Setup.

Prerequisites

  • A paid Apple Developer Program account (a real device is required for TestFlight and sandbox purchase testing).
  • An App Store Connect app record with a Bundle ID matching Xcode, App Store Connect, and RevenueCat.
  • Completed Paid Apps agreement, bank account, and tax forms in App Store Connect (required before any in-app purchase will load).
  • A RevenueCat project with an App Store app configured.

The template's default bundle identifier is:

com.soarstarter.SoarStarterSwift

Change every reference if your app uses a different Bundle ID.

Pre-flight: replace the template's placeholders

Before your first archive, walk the placeholders the template ships with. These are safe defaults for local development but must not reach the App Store:

AreaWhat to changeWhere
Bundle IDYour reverse-DNS identifier, matching App Store Connect + RevenueCatXcode target, PRODUCT_BUNDLE_IDENTIFIER
Signing teamYour Release distribution teamSigning & Capabilities
Version / buildBump the build number before every upload (1.0 (1)1.0 (2))Xcode target
Universal LinksReplace the applinks:example.com placeholder with your real domain and host the AASA fileEntitlement + Deep Links
Push environmentaps-environment = production for App Store buildsEntitlement + Push
Legal URLsReal TERMS_URL / PRIVACY_URL / EULA_URL (the Consent gate and App Review both need working links)Secrets.xcconfig
App icon + launchReplace the placeholder AppIcon / launch assetsAssets (Theming)
Display namePRODUCT_NAMEXcode target

For TestFlight and App Store builds, REVENUECAT_IOS_KEY must be the App Store app public SDK key (starts with appl_). RevenueCat intentionally crashes Release builds that use a simulated-store key (starts with test_). See Purchase Testing.

Archive and upload to TestFlight

Prepare the target

Open SoarStarterSwift.xcodeproj. Under Signing & Capabilities select your Release team and confirm the Bundle Identifier matches App Store Connect. Increase the build number — every upload needs a higher build than the last.

Archive

Select a generic iOS device (or a real device) as the run destination, then run Product → Archive. xcodebuild archiving is also fine, but Organizer is the smoother path for distribution.

Distribute

In Organizer, select the archive → Distribute AppApp Store ConnectUpload, and continue through signing.

If Xcode reports Upload Symbols Failed — The archive did not include a dSYM for Sentry.framework, the upload still succeeds. It only affects crash symbolication for that framework, not installation or purchases — see Observability.

TestFlight

In App Store Connect → your app → TestFlight, wait for processing, resolve Missing Compliance if shown, create an internal testing group, add the build, and install from the TestFlight app on a device.

For encryption compliance, if the app only uses standard system networking (HTTPS, Apple sign-in, RevenueCat, Supabase, Sentry, PostHog), answer that it does not use non-exempt proprietary encryption. You can pre-declare this in Info.plist:

<key>ITSAppUsesNonExemptEncryption</key>
<false/>

App Privacy labels

App Review requires you to declare what data the app collects. Map it to what the template's SDKs actually do — don't over- or under-declare:

SDKCollectsNotes
SupabaseAccount data (email, user ID), plus whatever your tables storeLinked to identity
RevenueCatPurchase history, a device/user identifierLinked to identity
PostHogProduct analytics / usage eventsConsent-gated — nothing sent before the Consent gate; can be disabled
SentryCrash data, diagnosticsUsed for app functionality

Because analytics only start after the consent gate, and both PostHog and Sentry no-op without keys, your labels should reflect your configured build — if you ship without a POSTHOG_API_KEY, you're not collecting analytics.

Review notes

Two things App Review specifically looks for, both already wired in the template:

  • Account deletion is available in-app — the delete-account Edge Function behind Account & Security (Example Features). Apple requires any app with account creation to offer in-app deletion. Point the reviewer to it.
  • The consent gate blocks tracking until the user agrees — mention it if asked about the App Privacy labels.

If your app gates features behind a subscription, provide a demo account or a note on how to reach the paywall so the reviewer can exercise it.

First submission: in-app purchase

The first in-app purchase must be submitted with a new app version. App Store Connect will say:

Your first in-app purchase must be submitted with a new app version.

Attach the in-app purchase on the app version page before submitting. For TestFlight sandbox testing alone, a product in Ready to Submit state is usually enough once agreements, tax, banking, product metadata, and RevenueCat are correct.

Release checklist

  • Bundle ID / signing team / build number set for Release.
  • REVENUECAT_IOS_KEY is the appl_ App Store key, not a test_ key.
  • REVENUECAT_PRO_ENTITLEMENT_ID exactly matches the RevenueCat entitlement identifier (template default: pro).
  • Products (soar_starter_swift_pro_monthly / _pro_yearly / _lifetime) are Ready to Submit, Cleared for Sale, with price + metadata + review screenshot.
  • aps-environment = production and the RevenueCat webhook points at the production Supabase project (Supabase Setup).
  • applinks: entitlement uses your real domain with a hosted AASA file.
  • Legal URLs, app icon, and display name are yours, not placeholders.
  • Update-gate strategy set: MIN_APP_VERSION / LATEST_APP_VERSION and APP_STORE_URL (fill APP_STORE_URL once the App Store ID exists) — see App Gates.
  • App Privacy labels match the SDKs your build actually configures.
  • In-app account deletion verified working for the reviewer.

On this page