Publishing
Publishing an Ionic app to the App Store and Google Play means going through Capacitor + the platform-specific signing flow, then submitting via Transporter, Xcode, or the Play Console. The whole pipeline becomes one command per release once fastlane is wired up. The work is one-time; the gains are every release after.
App Store + Play release pipelines with fastlane
EXAMPLE
# ===== Prerequisites =====
# - Apple Developer account (paid)
# - Google Play Console account (paid one-off)
# - macOS for iOS builds (App Store Connect needs Xcode)
# - fastlane installed: brew install fastlane
# ===== 1) Bump version + build numbers consistently =====
# capacitor.config.ts version: '1.4.0'
# android/app/build.gradle versionName '1.4.0' versionCode 142
# ios/App/App/Info.plist CFBundleShortVersionString 1.4.0 / CFBundleVersion 142
#
# Tip: source the version from a single file (package.json) and template the rest.
# fastlane has the 'increment_version_number' / 'increment_build_number' actions.
# ===== 2) Build the web bundle once =====
ionic build --prod
npx cap sync # copies web -> ios and android, updates plugins
# ===== iOS — App Store =====
# ios/fastlane/Fastfile
# default_platform(:ios)
# platform :ios do
# desc 'Build + submit to TestFlight'
# lane :beta do
# match(type: 'appstore', readonly: true) # signing via Match (encrypted git repo)
# build_app(scheme: 'App', workspace: 'App.xcworkspace', export_method: 'app-store')
# upload_to_testflight(skip_waiting_for_build_processing: true)
# end
# end
#
# Run:
cd ios && bundle exec fastlane beta
# Then in App Store Connect:
# - Add release notes per locale
# - Submit for App Review (or auto-release after approval)
# - First release? Prepare screenshots (6.5\" + iPad), privacy nutrition label, support URL.
# ===== Android — Google Play =====
# android/fastlane/Fastfile
# default_platform(:android)
# platform :android do
# desc 'Build AAB + upload to Internal track'
# lane :internal do
# gradle(task: 'bundleRelease')
# upload_to_play_store(track: 'internal',
# aab: '../app/build/outputs/bundle/release/app-release.aab',
# json_key: ENV['PLAY_JSON_KEY'])
# end
#
# desc 'Promote internal -> production'
# lane :promote do
# upload_to_play_store(track: 'production', track_promote_to: 'production',
# json_key: ENV['PLAY_JSON_KEY'])
# end
# end
#
# Run:
cd android && bundle exec fastlane internal
# Promote when you are ready:
cd android && bundle exec fastlane promote
# ===== Asset checklist (App Store + Play) =====
# - App icon (1024x1024 for iOS; 512x512 + adaptive for Android)
# - 6.5\" / 5.5\" screenshots (iOS); phone + 7\"/10\" tablet (Play)
# - Promo text + description per locale
# - Privacy policy URL
# - Data safety form (Play) / privacy nutrition label (App Store)
# - Age rating questionnaire
# - Test account credentials if your app gates features behind login
# ===== Common rejections =====
# - 4.0 Design (iOS): missing legal info, broken links, low value
# - 5.1.1 Privacy: collecting data without a privacy policy
# - 'Missing 64-bit native code' (Play): include arm64-v8a in abiFilters
# - 'Target API too low' (Play): bump targetSdkVersion to the current floor
# - Crash on review device: enable Sentry/Crashlytics, fix BEFORE resubmitting
# ===== CI integration =====
# .github/workflows/release.yml — fastlane runs unattended on macOS runners
# steps:
# - uses: actions/checkout@v4
# - uses: ruby/setup-ruby@v1
# - run: bundle install
# - run: cd ios && bundle exec fastlane beta
# env:
# MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
# APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.ASC_API_KEY }}
# ===== Post-launch =====
# - Crashlytics on day one (see firebase/crashlytics lesson)
# - Crash-free user rate >= 99.5% before scaling marketing
# - Phased rollout on Play (5% -> 25% -> 100%) for first week
# - TestFlight 'internal' before 'external' to catch obvious regressions
Why it matters
Get fastlane match working on day one. Shared, encrypted signing materials in a git repo means every release can be built from any developer machine or CI runner with no manual key handoffs — and re-issuing a leaked key is a one-command rotate instead of a coordination event across the team.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# iOS → App Store Connect / TestFlight # Android → Play Console (internal → closed → open → production)Try it Yourself »
Discussion
Loading…