Uploading from GitHub Actions
Last updated 16 August 2026
The appdropper-io/upload-action step sends a build to App Dropper and, on a pull request, posts the install link and a scannable QR code as a comment. A reviewer can install the branch on their phone without leaving the PR.
Get a token first
Nothing here works without one. See Set up CI uploads.
1. Add the secret
Open Settings → Secrets and variables → Actions
In the repository that builds the app.
New repository secret
Name it APPDROPPER_TOKEN and paste the token.
2. Add the step
- name: Upload to App Dropper
uses: appdropper-io/upload-action@v1
with:
file: build/app/outputs/flutter-apk/app-release.apk
token: ${{ secrets.APPDROPPER_TOKEN }}
release-notes: ${{ github.event.head_commit.message }}That’s the whole integration. Everything below is optional refinement.
A full Android workflow
name: Beta build
on:
push:
branches: [main]
pull_request:
# Required for the PR comment. Without it the upload still succeeds and
# the comment step is skipped.
permissions:
contents: read
pull-requests: write
jobs:
android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- uses: subosito/flutter-action@v2
with:
channel: stable
- run: flutter pub get
- run: flutter build apk --release
- name: Upload to App Dropper
id: appdropper
uses: appdropper-io/upload-action@v1
with:
file: build/app/outputs/flutter-apk/app-release.apk
token: ${{ secrets.APPDROPPER_TOKEN }}
- name: Print the link
run: echo "Install at ${{ steps.appdropper.outputs.install-url }}"A full iOS workflow
Signing is the hard part of iOS in CI, and App Dropper doesn’t change it — however you produce a signed .ipa today (Fastlane match, an imported certificate, an export options plist) still applies. Point the step at the resulting file:
jobs:
ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
# …your existing signing + archive + export steps…
- name: Upload to App Dropper
uses: appdropper-io/upload-action@v1
with:
file: build/ios/ipa/MyApp.ipa
token: ${{ secrets.APPDROPPER_TOKEN }}
tag: adhocAn ad-hoc .ipa only installs on devices already in its provisioning profile. App Dropper shows which UDIDs a build covers — see iOS UDIDs for ad-hoc builds.
Inputs
| Input | Default | What it does |
|---|---|---|
file | required | Path to the .apk or .ipa, relative to the workspace |
token | required | Your App Dropper API token, from a repository secret |
release-notes | head commit message | Notes shown to testers on the install page |
tag | beta | Label for the build, e.g. nightly |
comment-on-pr | true | Post the install link as a PR comment |
timeout | 600 | Seconds to wait for processing before failing |
cli-version | 1 | Version of the appdropper npm package to run |
Outputs
| Output | Example |
|---|---|
install-url | https://appdropper.io/my-app?build=aBc123 |
build-id | aBc123XyZ |
qr-url | A PNG QR code pointing at the install link |
Use them in later steps via ${{ steps.<id>.outputs.install-url }} — for a Slack message, a status check, or a deployment record.
PR comments
On a pull_request event the action posts a comment with the install link, the artifact name, the commit SHA and a QR image. On the next push it edits that same commentrather than adding another, so a long-running PR doesn’t collect a wall of them.
Two things it needs:
permissions: pull-requests: writein the workflow (or job). Without it the upload still succeeds and the comment is skipped.- A pull request from a branch in the same repository. GitHub gives fork-based PRs a read-only token and withholds secrets from them entirely, so neither the upload nor the comment can run there — a GitHub security boundary, not an App Dropper limitation.
Set comment-on-pr: false to turn comments off. The job summary — same link and QR code, on the workflow run page — is always written and needs no permissions.
Upload only when you need to
Uploads count against your plan’s hourly limit and your storage, so most teams don’t want one per commit on every branch:
# Only on main, or on PRs explicitly labelled for testing
- name: Upload to App Dropper
if: >-
github.ref == 'refs/heads/main' ||
contains(github.event.pull_request.labels.*.name, 'needs-testing')
uses: appdropper-io/upload-action@v1
with:
file: build/app/outputs/flutter-apk/app-release.apk
token: ${{ secrets.APPDROPPER_TOKEN }}Using the CLI instead
The action is a thin wrapper around the CLI, and using the CLI directly is equally supported — you lose the PR comment and gain nothing to maintain:
- name: Upload to App Dropper
env:
APPDROPPER_TOKEN: ${{ secrets.APPDROPPER_TOKEN }}
run: npx appdropper upload build/app/outputs/flutter-apk/app-release.apkEven here you get install-url, build-id and qr-url as step outputs — the CLI writes them to $GITHUB_OUTPUTwhen it detects it’s running in Actions.
Troubleshooting
The comment never appears
Almost always a missing pull-requests: write permission, or a PR from a fork. Check the workflow run — the comment step shows as skipped rather than failed.
“No such file”
The filepath is relative to the workspace root, not to the previous step’s directory. Add a run: ls -la build/… step before the upload to see what the build actually produced — Flutter, Gradle and Xcode all put artifacts in different places, and the path changes with build flavours.
The step fails but the build uploaded
A timeout during processing. The build is safe and will appear in your dashboard; raise timeout if your builds routinely need longer than ten minutes to parse.
Full flag list is in the CLI reference.
Ready to try it?
Drop an .apk or .ipa and get a shareable install link in seconds.