flutter run gives you a debug build with hot reload; flutter build produces a release build (AOT-compiled, minified) packaged for one platform: an Android app bundle or APK, an iOS .ipa, or a build/web folder. Before shipping you configure signing (a keystore referenced from key.properties on Android, your Apple team in Xcode on iOS) and bump the version in pubspec.yaml.
- Three build modes: debug (flutter run; JIT, hot reload, assertions, debugging services), profile (--profile; release performance plus profiling hooks) and release (flutter build defaults to it; AOT, no assertions, minified). Branch on kDebugMode / kProfileMode / kReleaseMode when code must differ.
- Android: flutter build appbundle writes build/app/outputs/bundle/release/app.aab (what Google Play wants); flutter build apk --split-per-abi writes one APK per ABI for stores that need APKs. R8 shrinking is on for release builds.
- Android signing: create an upload keystore with keytool, point android/key.properties at it, load that file in android/app/build.gradle.kts and use the release signingConfig; Play App Signing holds the real app signing key. Never commit the keystore or key.properties.
- iOS: register a Bundle ID and an App Store Connect record, set Team and 'Automatically manage signing' on the Runner target in Xcode, then flutter build ipa produces an .xcarchive and an .ipa to upload with Transporter, xcrun altool or Xcode; TestFlight distributes test builds.
- Versioning lives in pubspec.yaml as version: 1.0.0+1: the part before + is the build name (versionName / CFBundleShortVersionString), the part after is the build number (versionCode / CFBundleVersion). Override per build with --build-name and --build-number.
- Web: flutter build web compiles to JavaScript into build/web; --wasm compiles to WebAssembly (multithreaded rendering, needs COEP/COOP headers from your server); release builds minify and tree-shake; --source-maps emits .map files that must not be publicly hosted. --obfuscate --split-debug-info=dir hides Dart symbols on mobile and desktop.
The build mode decides how Dart is compiled; the target decides the artifact. Only release artifacts go to users.
Rehearsal, dress rehearsal, opening night
Debug mode is the rehearsal: you can stop mid-scene, change a line and pick up where you were (hot reload), and the director's notes (assertions, inspector) are everywhere. Profile mode is the dress rehearsal with a stopwatch: real costumes and lighting, so timings are meaningful, but a few instruments are still in the room. Release mode is opening night: compiled ahead of time, stripped of notes, and the only thing the audience ever sees. Signing is the venue's seal on the programme: stores refuse a performance that does not carry it.
Minimal working example
# 1. Set the version in pubspec.yaml: version: 1.4.0+23
# build-name = 1.4.0 (user facing), build-number = 23 (must increase)
# 2. Android: flutter build defaults to --release
flutter build appbundle
# -> build/app/outputs/bundle/release/app.aab (upload to Google Play)
flutter build apk --split-per-abi
# -> build/app/outputs/flutter-apk/app-arm64-v8a-release.apk (+ armeabi-v7a, x86_64)
# 3. iOS (requires macOS + Xcode): archive and App Store .ipa in one step
flutter build ipa
# -> build/ios/archive/*.xcarchive and build/ios/ipa/*.ipa
flutter build ipa --export-method ad-hoc # for devices outside the App Store
# 4. Web: JavaScript by default, WebAssembly with --wasm
flutter build web
flutter build web --wasm
# -> build/web (serve it from any static host)
# Override the pubspec version from CI
flutter build appbundle --build-name=1.4.0 --build-number=23
# Profile build for performance work on a real device
flutter run --profile
# Hide Dart symbols; keep the symbol files to decode crash reports later
flutter build ipa --obfuscate --split-debug-info=build/symbols- L1One line in pubspec.yaml feeds both Android (versionName/versionCode) and iOS (CFBundleShortVersionString/CFBundleVersion).
- L5App bundles are the Play Store's preferred format; Google generates device-specific APKs from them.
- L8Without --split-per-abi you get one fat APK with every ABI, larger for every user.
- L12ipa builds need the Xcode signing settings to be complete; the archive lands in build/ios/archive.
- L14ad-hoc, development and enterprise export methods exist for distribution outside the App Store.
- L18Wasm builds render on multiple threads and require Cross-Origin-Embedder-Policy and Cross-Origin-Opener-Policy headers from the server.
- L22Flags override pubspec.yaml for this build only, which keeps CI version numbering out of source control.
- L25Never judge performance in debug mode; profile mode compiles like release but keeps the profiling tools.
- L28Obfuscation renames Dart symbols; --split-debug-info writes the mapping you need for flutter symbolize.
Use cases
- Shipping to Google Play: build an app bundle, sign it with an upload key and let Play App Signing manage the distribution key.
- Shipping to the App Store or TestFlight: configure signing in Xcode once, then let flutter build ipa and App Store Connect handle archives and uploads.
- Hosting a web app: produce build/web (JavaScript or Wasm), set the right headers and upload to any static host or Firebase Hosting.
- Automating releases in CI: drive version numbers with --build-name/--build-number and keep secrets (keystore, key.properties, App Store Connect API key) out of the repository.
Benefits
- One command per platform; the Flutter tool drives Gradle, Xcode and the web compilers for you.
- A single version line in pubspec.yaml maps to both platforms' versioning schemes.
- Release builds are AOT-compiled and shrunk by default, so startup time and binary size are good without extra configuration.
Limitations
- iOS and macOS builds require a Mac with Xcode and an Apple Developer Program membership; there is no way around it for App Store distribution.
- Signing material is irreplaceable: losing an upload key or the Apple certificates means a store support process, not a rebuild.
- Obfuscation renames Dart symbols only; it does not encrypt assets or protect native code, and stack traces become unreadable unless you keep the split-debug-info output.
- Wasm builds need COEP/COOP headers and modern browsers; keep the JavaScript build as the fallback until your hosting supports them.
Alternatives and trade-offs
- CI/CD services (Codemagic, GitHub Actions, Fastlane) — You release often or from several machines; they run the same flutter build commands with managed secrets and upload steps.
- flutter install / ad-hoc exports — You only need builds on a handful of test devices rather than a store release.
- Firebase Hosting with the framework-aware CLI — For web, firebase deploy can run flutter build web --release for you and host the result.
- Performance profiling in profile mode — The question is 'why is it slow?' rather than 'how do I ship?'; use --profile with DevTools before touching release settings.
Committing the keystore or key.properties
Problem: Anyone with the upload key and passwords can publish an update under your app's identity. Public repositories with keystores are scanned routinely.
Fix: Keep upload-keystore.jks outside the project (or ignored), add key.properties and *.jks to .gitignore, and inject them in CI from secret storage.
android/key.properties # committed
android/app/upload-keystore.jks # committedkey.properties
**/*.keystore
**/*.jksUploading a build with the same build number
Problem: App Store Connect rejects an upload whose CFBundleVersion was already used for that version, and Google Play rejects an APK/AAB whose versionCode is not higher than the previous one.
Fix: Increase the build number (the part after +) for every upload, or pass --build-number from CI with a monotonically increasing value such as the pipeline run number.
version: 1.4.1+23 # 23 was already uploadedversion: 1.4.1+24 # 1.4.1 = build-name, 24 = build-numberMeasuring performance in a debug build
Problem: Debug builds run Dart in a JIT with assertions and debugging services enabled, so frame times and startup are nothing like what users see; 'jank' found this way often disappears in release.
Fix: Use flutter run --profile on a physical device and open DevTools; use release builds for final numbers. Guard debug-only work with kDebugMode so it is tree-shaken out of release.
Deploying a Wasm build or source maps without checking the host
Problem: A --wasm build needs Cross-Origin-Embedder-Policy and Cross-Origin-Opener-Policy response headers; without them the app falls back or fails to start. Publicly hosted .map files from --source-maps expose un-minified symbol names and file paths.
Fix: Configure the headers on your host (or stay on the JavaScript build), and upload .map files only to your crash-reporting service while excluding them from public hosting.
This page is Boring Doc’s independent explanation. For the complete specification use the official pages below (content © The Flutter Authors, CC BY 3.0; code samples BSD). Dates show when the explanation was last checked and when the official page last changed.
- https://docs.flutter.dev/deployment
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/deployment/android
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/deployment/ios
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
- https://docs.flutter.dev/deployment/web
- Last checked
- Oct 21, 2026
- Source last updated
- 3 weeks ago
- Last synchronized
- 3 weeks ago
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.