GitHub Actions

Automate AppFlight uploads on every push using GitHub Actions.

Setup

  1. Generate an API key — AppFlight mobile app → Settings → API Keys → + — label it GitHub Actions
  2. Add secret — GitHub repo → Settings → Secrets and variables → Actions → New repository secret → APPFLIGHT_API_KEY
  3. Commit appflight.json to your repository (run appflight init locally 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:

CodeMeaningFix
4APK not foundCheck apkPath in appflight.json matches your build output
5Version already uploadedBump version in pubspec.yaml (Flutter) or android/app/build.gradle (React Native)
6API key invalid or revokedRegenerate key in AppFlight mobile → Settings → API Keys
8APK limit reached (plan limit)Upgrade to First Class in the mobile app