Skip to main content
MAG&Cie
Back to tutorials
FreeIntermediateDevelopment

Publishing a Mobile App on the App Store and Google Play: Step-by-Step Guide

Step-by-step guide to publish an iOS and Android app on the stores in 2026: developer accounts, certificates, signing, asset preparation, TestFlight, Internal Testing, review submission, rejection handling, post-publication versioning. Realistic timeline, checklists, pitfalls.

September 21, 202645 min

Prerequisites

  • A working app running in dev on iOS + Android simulator
  • Apple ID + DUNS number if legal entity (2 to 5 business days to obtain)
  • Google account + credit card for the $25 Play Console setup
  • Privacy policy published at a stable HTTP 200 URL
  • 1024×1024 vector icon and minimal visual charter
  • Xcode ≥ 15.3 (iOS 17.4+) or EAS Build pipeline configured
On this page10

Publishing an app on both stores in 2026 is no longer a formality — it's a parallel workstream starting 4 weeks before launch and replaying at every version. This guide gives the exact timeline, concrete commands, and traps you pay for twice if you discover them in production.

D-30: developer accounts. Create Apple Developer Program and Google Play Console. Count 2 to 5 business days for Apple DUNS verification (company) and 24 to 48 h for Google identity verification.

D-21: technical setup. iOS certificates (Distribution + App Store Provisioning), Play App Signing (upload keystore), CI/CD pipelines (EAS Build or GitHub Actions + fastlane).

D-14: assets and metadata. Icons, screenshots, feature graphic, description texts in both target locales, privacy policy online, Data Safety form filled, Privacy Manifest added.

D-7: first submission. Upload build → TestFlight (internal + external if needed) → 48 h of testing on internal channels → Submit for Review on Apple side + Closed Testing then Production on Google side.

D-0: launch. Approval received, phased release enabled, crash report monitoring, response to first reviews.

D+7 to D+30: iterate. Version 1.0.1 for fixes reported in the week, plan version 1.1 with user requests.

Apple setup: the details

Create an App ID.

In developer.apple.com → Certificates, Identifiers & Profiles → Identifiers → new App ID of type App:

  • Bundle ID: reverse-DNS (e.g. com.mag-cie.myopenspot). IRREVERSIBLE — change = new app, lose reviews and downloads. Choose carefully.
  • Capabilities to enable per need (Push Notifications, Sign in with Apple, In-App Purchase, Associated Domains…). Toggleable later, but requires new provisioning profile.

Create the distribution certificate.

Certificates → new → Apple Distribution (2020+ format, replaces iOS Distribution). Generate a CSR from Keychain Access on Mac, upload, download the .cer. Guard carefully — 12 months expiration, renew ahead.

Create the App Store provisioning profile.

Profiles → new → App Store → select App ID + certificate. Download the .mobileprovision, install in Xcode (or upload in EAS credentials).

Configure the app in App Store Connect.

appstoreconnect.apple.com → My Apps → new. Fill:

  • Platform: iOS (+ macOS, tvOS, visionOS if applicable)
  • Name: name shown on stores (30 chars max)
  • Primary Language: French or English per main locale
  • Bundle ID: the one created earlier
  • SKU: internal identifier (e.g. myopenspot-ios-2026)

Google Play setup: the details

Create the app in Play Console.

play.google.com/console → Create app. Fill:

  • Name
  • Default language
  • App or game
  • Free or paid (paid → free is irreversible)
  • Package name (reverse-DNS, IRREVERSIBLE)

Enable Play App Signing.

In Setup → App Signing. Google manages the final signing key (the one signing distributed APKs). You manage only an "upload key" to upload your AAB.

Generate the upload keystore:

Bash
keytool -genkey -v -keystore upload-keystore.jks \
  -keyalg RSA -keysize 2048 -validity 10000 -alias upload

Upload the public key (extract via keytool -export) to Google. Save the keystore + password in the company vault AND CI secrets.

Fill the Data Safety form.

Setup → Data safety. Manual, to update at every new version. Structure:

  • Data collection: global yes/no
  • Data sharing with third parties: global yes/no
  • For each data type collected (name, email, precise location, photos, contacts, generated content): category, purpose, required or optional, shared yes/no
  • Security: in-transit encryption yes/no, deletion on request yes/no, MPO for Kids compliance if applicable

Google cross-checks these declarations against static analysis of the uploaded AAB. A mismatch (e.g. ACCESS_FINE_LOCATION permission in manifest but form says "no location collected") → warning at minimum, publication block at worst.

iOS Privacy Manifest: mandatory since May 2024

Create PrivacyInfo.xcprivacy at the root of the bundle. Minimal .plist format:

XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSPrivacyAccessedAPITypes</key>
    <array>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>CA92.1</string>
            </array>
        </dict>
    </array>
    <key>NSPrivacyCollectedDataTypes</key>
    <array>
        <dict>
            <key>NSPrivacyCollectedDataType</key>
            <string>NSPrivacyCollectedDataTypeEmailAddress</string>
            <key>NSPrivacyCollectedDataTypeLinked</key>
            <true/>
            <key>NSPrivacyCollectedDataTypeTracking</key>
            <false/>
            <key>NSPrivacyCollectedDataTypePurposes</key>
            <array>
                <string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
            </array>
        </dict>
    </array>
    <key>NSPrivacyTracking</key>
    <false/>
    <key>NSPrivacyTrackingDomains</key>
    <array/>
</dict>
</plist>

Also add third-party SDK manifests (Sentry, RevenueCat, Firebase…) — these SDKs normally provide their own PrivacyInfo.xcprivacy embedded in their bundle since 2024.

Compilation and upload

iOS with native Xcode:

Bash
xcodebuild -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath build/MyApp.xcarchive \
  archive

xcodebuild -exportArchive \
  -archivePath build/MyApp.xcarchive \
  -exportPath build/ \
  -exportOptionsPlist exportOptions.plist

xcrun altool --upload-app -f build/MyApp.ipa \
  --type ios \
  --apiKey $ASC_API_KEY --apiIssuer $ASC_API_ISSUER

iOS + Android with EAS:

Bash
eas build --platform all --profile production
eas submit --platform all --latest

One command, both stores. Configuration in eas.json:

JSON
{
  "build": {
    "production": {
      "ios": { "resourceClass": "m-medium" },
      "android": { "buildType": "app-bundle" }
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "you@company.com",
        "ascAppId": "1234567890",
        "appleTeamId": "ABCDE12345"
      },
      "android": {
        "serviceAccountKeyPath": "./play-service-account.json",
        "track": "internal"
      }
    }
  }
}

TestFlight and Internal Testing

iOS — Internal TestFlight (instant): in App Store Connect → TestFlight → select the build → activate for internal testers (up to 100 members of your Apple Developer team). Instant push notification on their iPhone (TestFlight app installed).

iOS — External TestFlight (24 to 48 h): add an external group, invite up to 10,000 emails or generate a public link. Submit the build to TestFlight review (distinct from App Store review, faster and more tolerant). Once approved, external testers receive the invitation.

Android — Internal Testing (instant): Play Console → Testing → Internal testing → create a release → upload the AAB → add the Google email list → publish. Send opt-in link to testers. They receive the version in 5 to 15 minutes.

Android — Closed Testing (mandatory for new personal accounts): 12 distinct testers × 14 days minimum. Without this phase, no Production release possible.

Submission and review

Apple: in App Store Connect → select the version → fill "App Review Information" (test credentials if login required, notes for Apple: justify every sensitive permission, explain hidden features). Click Submit for Review. Track status in the Apple Developer app.

Statuses:

  • Waiting for Review: 4 to 24 h
  • In Review: 4 to 24 h
  • Pending Developer Release / Ready for Sale: approved — publish manually OR auto-release

Google: promote the Closed Testing release → Production. Google runs automatic review (30 min to 24 h) + sometimes human review (24 to 72 h). Track in Play Console → Publishing overview.

Handling an Apple rejection

Rejection arrives by email + notification in Resolution Center. It contains:

  • The guideline cited (e.g. 5.1.1)
  • A screenshot of the problem
  • Sometimes a request for additional info

Three possible responses:

  1. Reply with justification if the rejection is ambiguous. Factual tone, in English, explain how your app respects the guideline. Apple re-reads within 24 to 48 h.

  2. Fix and re-submit if the rejection is legitimate. Fix, increment buildNumber, upload new build, re-submit.

  3. Escalate to the Appeals Board if the rejection is truly unjustified. Rare (< 5% of cases), heavy procedure, reserve for critical blocks.

Never argue emotionally in the Resolution Center — the trace stays on your developer account and weighs on future reviews.

After publication

Phased Release (iOS): enable in App Store Connect before publishing. Apple ramps the new version to 1%, 2%, 5%, 10%, 20%, 50%, 100% over 7 days. On massive crash detected on early users, ability to pause or pull.

Staged Rollout (Android): enable manually in Play Console. Start at 10%, increase to 25%, 50%, 100% over 3 to 5 days while monitoring crash reports.

Monitoring: Sentry or equivalent connected to native iOS/Android crash reports + JS/React Native. Tolerance threshold: crash-free sessions > 99.5%. Below → rollback or immediate hotfix.

Review responses: Apple and Google penalize inaction. Answer within 48 h to 1 to 3 star reviews, briefly thank 4 to 5 star ones. A user can revise a review upward after an answer.

Next versions: iterate every 2 to 4 weeks to stay visible in stores. An app without update for 6 months sees its ranking drop automatically.

Operational summary

StepRealistic durationMain blocker
Developer accounts2 to 5 daysApple DUNS
Certificates + signing1 to 2 hoursConfiguration errors
Visual assets4 to 8 hoursConsistent screenshots
Metadata + Data Safety2 to 4 hoursCode / declaration consistency
iOS Privacy Manifest1 to 2 hoursUndeclared third-party SDKs
Compile + upload30 minInconsistent build number
TestFlight + Internal Testing2 to 4 daysTester availability
Apple + Google review1 to 3 daysRejection 2.3, 5.1.1, 4.3
Publication + phased release15 min + 7 daysCrash monitoring
Minimum total7 to 14 daysApple rejection

Publishing an app is a craft. This guide gives the map. The terrain, you learn by publishing.

Want to delegate? See our application development service — setup + first publication + skill transfer in 3 weeks.