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
| Flag | Description |
|---|---|
--flavor, -f | Flavor to upload. Required when the project has multiple flavors configured. |
--file | Path to the APK. Overrides apkPath from appflight.json. |
--version | Version string. Overrides the version read from your project files. |
--build-number | Build number. Overrides the build number from your project files. |
--dry-run | Print what would be uploaded without actually uploading. |
--ci | CI 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
versionfrompubspec.yaml(e.g.1.2.3+45) - React Native โ reads
versionName+versionCodefromandroid/app/build.gradleand combines them as1.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
| Code | Meaning |
|---|---|
0 | Upload succeeded |
2 | appflight.json not found โ run init first |
3 | Not logged in โ run login or set APPFLIGHT_API_KEY |
4 | APK file not found โ build the APK first |
5 | Version already exists โ bump the version |
6 | API key invalid or revoked โ run login again |
7 | Storage upload failed |
8 | APK limit reached โ upgrade your plan |
Plan limits
AppFlight enforces per-app APK limits based on your subscription:
| Plan | APK limit per app | On limit hit |
|---|---|---|
| Economy (free) | 5 (individual) ยท 10 (org) | Upload blocked โ CLI exits 8 |
| First Class | 15 (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
POST /v1/cli/upload-urlโ requests a signed write URL from AppFlightPUT <signedUrl>โ streams the APK directly to Firebase StoragePOST /v1/upload-apkโ registers metadata; AppFlight notifies testers
No APK bytes pass through the AppFlight server, so there is no file size ceiling.