Both stores require an account, signing, and a review before your app reaches real users. Neither
process is instant the first time through; budget for it separately from development time.

## iOS: Archive and App Store Connect

You need a Distribution certificate to Archive, and, surprisingly, automatic signing needs at least
one registered device even for an App Store build, not just for local testing. On a brand-new team
with zero devices registered, Product, Archive fails with an error about having no devices from
which to generate a provisioning profile. The message is misleading: the real block is that
automatic signing can't create *any* profile at all with zero devices on the account.

To fix it:

1. Create an Apple Distribution certificate: Xcode, Settings, Accounts, your team, Manage
   Certificates, then add an Apple Distribution certificate.
2. Register one device: plug in an iPhone, enable Developer Mode on iOS 16 and later (Settings,
   Privacy & Security, Developer Mode, on, then restart), let Xcode register it, and retry Signing
   & Capabilities. You can also add a device UDID manually at developer.apple.com under Devices.
3. Then Product, Archive (with the destination set to Any iOS Device, not a simulator), Organizer,
   Distribute App, App Store Connect, Upload. The archive itself is signed with the Distribution
   certificate; no device is required for the actual distribution, registering one only unblocks
   Xcode's automatic signing flow.

If you'd rather not register a device, uncheck "Automatically manage signing" and create an App
Store provisioning profile manually for your bundle id at developer.apple.com, then download and
select it in Xcode.

Store submission needs an active Apple Developer account and passing App Review. If your app
includes in-app purchases, the Paid Applications Agreement has to be active under App Store
Connect, Business, or purchases fail in both sandbox and production even after your app is
approved.

## Android: signing and Play Console

Create an upload keystore and read it from a gitignored `keystore.properties` file, so signing
credentials never end up in the repository:

```bash
keytool -genkeypair -v -keystore ~/keystores/upload.jks \
  -alias upload -keyalg RSA -keysize 2048 -validity 10000
```

`keystore.properties` (at the module root; gitignore this file and any `.jks` alongside it):

```properties
storeFile=/absolute/path/to/upload.jks
storePassword=...
keyAlias=upload
keyPassword=...
```

`app/build.gradle.kts` reads this file if it exists and configures a `release` signing config from
it, guarded so CI and a fresh clone still build unsigned when the file is absent. On your first
upload, accept Play App Signing: Google holds the actual app signing key, and your keystore is only
the upload key you sign with locally.

A few things that catch people out:

- **Bump `versionCode` on every upload**, to any track, including internal testing. Play rejects a
  reused code outright. `versionName`, the user-facing version string, doesn't need to change for
  every internal bump.
- **License testing avoids real charges.** Internal-testing purchases are real money unless the
  tester's Google account is added under Play Console, Setup, License testing. Add your testers
  there first; their purchases then use a free test card and renew on an accelerated clock, which
  is what makes it practical to exercise the full purchase-to-entitlement-to-notification loop
  before launch.
- **Products can't be created until a build exists.** Play won't let you create subscription
  products until you've uploaded a bundle that includes the Play Billing library to at least one
  track. Upload to Internal testing first, then create the products (matching the ids in
  `InApp::PRODUCT_PLANS`).

## Next

You've now covered the full mobile picture. Return to
[Mobile Overview](/docs/mobile-apps/mobile-overview), or move on to
[Deploying with Kamal](/docs/deployment/deploying-with-kamal) for the web side of shipping.
