Vibeloy
Sign Up
All posts

Blog

Publishing a Flutter App to the Play Store and App Store: Step-by-Step Guide

Published: Updated: 9 min read

Pre-launch checklist

The applicationId (Android) and Bundle ID (iOS) are your app's permanent identity on each store; neither can be changed after the first upload. Finalize both before you start publishing — changing them later means opening a brand-new listing from zero, losing your existing reviews and install count.

In Flutter, the version number lives in pubspec.yaml's version: x.y.z+N field; x.y.z is the user-facing version, and N is the build number, which maps to both the Android versionCode and the iOS build number at once. Uploading without incrementing N on every new store submission gets rejected.

Icon requirements differ between the two stores: Play wants a 512x512 pixel high-res icon with an alpha channel (transparency), while the App Store expects a 1024x1024 pixel PNG with NO alpha channel. With the flutter_launcher_icons package you can generate both from a single source image with one command.

Android: building the app bundle and the Play Console flow

The flutter build appbundle --release command produces the store-ready package at build/app/outputs/bundle/release/app-release.aab. The Play Store now only accepts the .aab format for new uploads; you don't need to produce a separate .apk.

Personal (individual) Play Console accounts opened after November 13, 2023 must complete an uninterrupted 14-day closed test with at least 12 active testers before gaining production access. This requirement does not apply to organization accounts or personal accounts opened earlier; check the current requirement in Play Console before applying, since the rule changes from time to time.

The very FIRST .aab uploaded to Play Console for an app must always be uploaded manually — no CLI or automated submission for that one; later releases can use a CLI or your own integration. When you go to production, starting with a staged rollout (e.g. 5-20% first, ramping up while watching Android vitals) rather than releasing to everyone at once reduces risk.

iOS: archiving with Xcode/EAS and the TestFlight flow

The flutter build ipa --release command produces a release-ready .ipa file under build/ios/ipa/. Archiving this build and sending it to App Store Connect requires a Mac (physical or cloud-based); Xcode does not run on Windows or Linux. If you don't own a Mac, cloud CI services such as Codemagic, GitHub Actions macOS runners, or MacStadium are options.

You can upload the resulting .ipa to App Store Connect either through Xcode Organizer's flow (Window > Organizer > Archives > Distribute App) or the standalone Transporter app; both produce the same result. Version (user-facing) and build number (must be unique and increasing per upload, cannot be reused within the same version) are two separate fields.

TestFlight offers two testing tiers: Internal testing (up to 100 people on your App Store Connect team) does NOT require Apple review and is available as soon as the build finishes processing. External testing targets testers joining via email or a public link (up to 10,000 per group); the FIRST build added to an app must pass Apple's "beta review" (which usually completes within a day), while subsequent builds may not require a full review.

Store metadata: visuals, description, and the data-safety form

On Play, character limits are strict: app name up to 30, short description up to 80, full description up to 4000 characters. On App Store Connect, the app name must be unique across the whole store, and there's also a subtitle and a keywords field to fill in. Screenshot sizes (1080x1920 for Play phone screenshots, exact pixel matches for the App Store's 6.9-inch iPhone class) follow different rules on each store.

Both stores require you to declare your data-collection behavior: Play's Data safety form, and the App Store's App Privacy labels (sometimes called the "nutrition label"). If you use an ad SDK, analytics, or crash reporting, you must check this accurately on both; neither panel lets you publish without a genuinely accessible, up-to-date privacy policy URL.

Both panels also require a support/contact field: on Play, an account-level support email and an optional website field; on App Store Connect, a mandatory Support URL and an optional Marketing URL. If you don't have a website for these fields, we cover practical options in a separate post.

Common rejection reasons and the Expo (EAS) difference

The most common Apple rejections are: Guideline 2.1(a) (crashes, blank screens, broken links, undeclared missing features, or not leaving a working demo account in Review Notes for a feature that requires login), Guideline 5.1.1 (empty or vague permission usage descriptions), and Guideline 4.3 (template apps resubmitted with minor tweaks). Testing end-to-end on a clean device before submitting prevents most of these three.

On Play, the most common issue is the Data safety form not matching actual SDK behavior, or an empty/unreachable privacy policy URL; both risk rejection or a later suspension. Using AdMob without app-ads.txt verification doesn't cause an outright rejection, but it can restrict ad serving and cost you revenue.

For projects using Expo (EAS), the flow is largely automated: npx eas-cli build --platform all --profile production builds for both stores in the cloud, and npx eas-cli submit automates submission (aside from Play's first-upload exception). Version numbers live in three separate fields in app.json — expo.version, expo.ios.buildNumber, and expo.android.versionCode — instead of Flutter's single pubspec.yaml field, and must be kept in sync by hand.

Frequently asked questions

What command do I run in Flutter before publishing to the Play Store?
flutter build appbundle --release; this produces the package you upload to Play Console at build/app/outputs/bundle/release/app-release.aab.
Do I need a Mac to publish to iOS?
Yes — archiving the build requires Xcode, and Xcode only runs on macOS. If you don't own a physical Mac, cloud CI services such as Codemagic, GitHub Actions macOS runners, or MacStadium can substitute; if you use Expo (EAS), the build already happens in the cloud, so you don't need your own Mac at all.
Does a build I upload to TestFlight reach testers immediately?
Yes for the internal group — no Apple review is required, and the build is usable as soon as it finishes processing. For the external group, only the first build added to an app must pass Apple's "beta review" (which usually completes within a day); subsequent builds may not require a full review.
Does Play Console's closed-testing requirement apply to everyone?
No. It only applies to personal (individual) accounts opened after November 13, 2023; organization accounts and personal accounts opened before that date are exempt.
What is the biggest difference between publishing with Expo (EAS) and Flutter?
Flutter builds happen locally (iOS strictly needs a Mac); Expo produces the build in the cloud via EAS and automates most of submission with its submit commands. Version numbers are also tracked differently: a single pubspec.yaml field in Flutter versus three separate fields in Expo's app.json.