upload

Uploads a built APK to AppFlight. This is the primary command.

Uploads a built APK to AppFlight. This is the primary command.

appflight upload [--flavor <name>] [options]

Flags

FlagDescription
--flavor, -fFlavor to upload. Required when the project has multiple flavors configured.
--filePath to the APK. Overrides apkPath from appflight.json.
--versionVersion string. Overrides the version read from your project files.
--build-numberBuild number. Overrides the build number from your project files.
--dry-runPrint what would be uploaded without actually uploading.
--ciCI mode: prefix log lines with [appflight], disable progress bar.

Usage

Flavored app โ€” --flavor is required:

appflight upload --flavor stage
appflight upload --flavor prod

No-flavor app:

appflight upload

Dry run first:

appflight upload --flavor stage --dry-run

CI mode:

appflight upload --flavor stage --ci

Also auto-detected when the CI environment variable is set.

Version resolution

The CLI reads the version automatically from your project files:

  • Flutter โ€” reads version from pubspec.yaml (e.g. 1.2.3+45)
  • React Native โ€” reads versionName + versionCode from android/app/build.gradle and combines them as 1.2.3+45

Override either at upload time:

appflight upload --flavor stage --version 1.2.3
appflight upload --flavor stage --build-number 99

Exit codes

CodeMeaning
0Upload succeeded
2appflight.json not found โ€” run init first
3Not logged in โ€” run login or set APPFLIGHT_API_KEY
4APK file not found โ€” build the APK first
5Version already exists โ€” bump the version
6API key invalid or revoked โ€” run login again
7Storage upload failed
8APK limit reached โ€” upgrade your plan

Plan limits

AppFlight enforces per-app APK limits based on your subscription:

PlanAPK limit per appOn limit hit
Economy (free)5 (individual) ยท 10 (org)Upload blocked โ€” CLI exits 8
First Class15 (individual) ยท 20 (org)Rollover โ€” oldest build auto-rotates, upload succeeds

Economy โ€” blocked at cap

When a free-tier user hits the cap, the upload is blocked and the CLI exits with code 8:

โœ— Upload blocked โ€” you've reached your APK limit for this app.
โœ— Free plan: 5 APKs per app. Upgrade in the AppFlight mobile app.
โœ— Settings โ†’ Subscription โ†’ Upgrade to First Class

Run with --verbose to see the full server response:

appflight upload --flavor stage --verbose

First Class โ€” rollover at cap

When a pro user is at the APK cap, the new build is registered and the oldest existing build is auto-removed (FIFO by upload time). The upload succeeds (exit code 0) and the CLI prints:

โœ“ v1.2.3 of com.your.app uploaded successfully.
Rolled over oldest build (v1.0.0) to stay at your plan cap.

Version uniqueness is still enforced โ€” rollover never bypasses the duplicate-version check (exit code 5). Rollover runs best-effort on the underlying storage blob: if the Storage delete fails, the Firestore record is still updated and a weekly sweep cleans up any orphans.

How it works internally

  1. POST /v1/cli/upload-url โ€” requests a signed write URL from AppFlight
  2. PUT <signedUrl> โ€” streams the APK directly to Firebase Storage
  3. POST /v1/upload-apk โ€” registers metadata; AppFlight notifies testers

No APK bytes pass through the AppFlight server, so there is no file size ceiling.