App Dropper

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

.github/workflows/beta.yml
- 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

.github/workflows/beta.yml
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:

.github/workflows/ios-beta.yml
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: adhoc

An 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

InputDefaultWhat it does
filerequiredPath to the .apk or .ipa, relative to the workspace
tokenrequiredYour App Dropper API token, from a repository secret
release-noteshead commit messageNotes shown to testers on the install page
tagbetaLabel for the build, e.g. nightly
comment-on-prtruePost the install link as a PR comment
timeout600Seconds to wait for processing before failing
cli-version1Version of the appdropper npm package to run

Outputs

OutputExample
install-urlhttps://appdropper.io/my-app?build=aBc123
build-idaBc123XyZ
qr-urlA 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: write in 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:

.github/workflows/beta.yml
# 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:

.github/workflows/beta.yml
- name: Upload to App Dropper
  env:
    APPDROPPER_TOKEN: ${{ secrets.APPDROPPER_TOKEN }}
  run: npx appdropper upload build/app/outputs/flutter-apk/app-release.apk

Even 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.

Upload a build