GitHub Actions
Automate AppFlight uploads on every push using GitHub Actions.
Setup
- Generate an API key — AppFlight mobile app → Settings → API Keys → + — label it
GitHub Actions - Add secret — GitHub repo → Settings → Secrets and variables → Actions → New repository secret →
APPFLIGHT_API_KEY - Commit
appflight.jsonto your repository (runappflight initlocally first)
Workflow
Create .github/workflows/appflight.yml:
name: Upload to AppFlight
on:
push:
branches: [main]
jobs:
build_and_upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.38.2' # pin your version — see note below
cache: true
- name: Setup Dart
uses: dart-lang/setup-dart@v1
- name: Install AppFlight CLI
run: |
dart pub global activate appflight_cli
echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
- name: Build APK
run: flutter build apk --flavor stage --release
- name: Upload to AppFlight
env:
APPFLIGHT_API_KEY: ${{ secrets.APPFLIGHT_API_KEY }}
run: appflight upload --flavor stage --ci
:::tip Why pin the Flutter version?
Using flutter-version: '3.x' resolves to whatever the latest stable is at build time, which can silently introduce breaking changes. Pin to the same version your team uses locally (e.g. 3.38.2). Update the pin deliberately when you upgrade.
:::
:::warning PATH for the installed binary
dart pub global activate appflight_cli installs the appflight binary into $HOME/.pub-cache/bin, which is not on the runner's PATH by default. The echo ... >> $GITHUB_PATH line above adds it for every subsequent step. Without it, the next step fails with appflight: command not found.
:::
:::info Java is required for the Android build
actions/setup-java@v4 is required for both Flutter and React Native — the Android Gradle Plugin runs on the JDK. Match the JDK your local Android Studio uses (17 is the current default).
:::
Multi-flavor
Install the CLI once, then repeat the build and upload steps per flavor:
- name: Install AppFlight CLI
run: |
dart pub global activate appflight_cli
echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
- name: Build stage APK
run: flutter build apk --flavor stage --release
- name: Upload stage
env:
APPFLIGHT_API_KEY: ${{ secrets.APPFLIGHT_API_KEY }}
run: appflight upload --flavor stage --ci
- name: Build qa APK
run: flutter build apk --flavor qa --release
- name: Upload qa
env:
APPFLIGHT_API_KEY: ${{ secrets.APPFLIGHT_API_KEY }}
run: appflight upload --flavor qa --ci
Faster builds
On ephemeral runners, Gradle caches are cold on every run. This optional step usually cuts ~1–2 minutes:
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('android/gradle/wrapper/gradle-wrapper.properties', 'android/app/build.gradle.kts') }}
restore-keys: gradle-${{ runner.os }}-
For Flutter projects, also cache pub packages:
- name: Cache pub packages
uses: actions/cache@v4
with:
path: ~/.pub-cache
key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }}
restore-keys: pub-${{ runner.os }}-
Place both after your SDK setup step and before Install AppFlight CLI.
Smaller, faster APKs for testing (Flutter)
For CI builds that only need to land on testers' devices (not the Play Store), an arm64-only APK is ~40% smaller and builds faster:
- name: Build APK
run: flutter build apk --flavor stage --release --target-platform android-arm64
Skip this for production Play Store builds — Play requires a universal AAB or the full set of ABIs.
Exit codes
If appflight upload exits non-zero the workflow fails. Common codes:
| Code | Meaning | Fix |
|---|---|---|
| 4 | APK not found | Check apkPath in appflight.json matches your build output |
| 5 | Version already uploaded | Bump version in pubspec.yaml (Flutter) or android/app/build.gradle (React Native) |
| 6 | API key invalid or revoked | Regenerate key in AppFlight mobile → Settings → API Keys |
| 8 | APK limit reached (plan limit) | Upgrade to First Class in the mobile app |