App Dropper

Uploading from CircleCI

Last updated 16 August 2026

CircleCI needs one runstep and one environment variable. There’s no orb to install.

Get a token first

Nothing here works without one. See Set up CI uploads.

1. Add the token

Two places it can go, depending on how many projects need it:

  • One project. Project Settings → Environment Variables → Add Environment Variable. Name it APPDROPPER_TOKEN.
  • Several projects. Organization Settings → Contexts. Create a context (say appdropper), add the variable there, and reference the context from each job. Contexts can be restricted to specific security groups, which is the better choice for a shared credential.

2. Add the upload step

Android

.circleci/config.yml
version: 2.1

jobs:
  build-android:
    docker:
      - image: cimg/android:2024.01-node
    steps:
      - checkout
      - run:
          name: Build release APK
          command: ./gradlew assembleRelease

      - run:
          name: Upload to App Dropper
          command: |
            npx appdropper upload \
              app/build/outputs/apk/release/app-release.apk \
              --notes "$(git log -1 --pretty=%B)"

workflows:
  beta:
    jobs:
      - build-android:
          context: appdropper   # omit if using a project variable

Note the -node image variant — the plain cimg/androidimages don’t all ship Node. On an image without it, install Node first or use the node orb.

iOS

.circleci/config.yml
jobs:
  build-ios:
    macos:
      xcode: 15.4.0
    steps:
      - checkout

      # …your existing signing + archive + export steps…

      - run:
          name: Upload to App Dropper
          command: |
            npx appdropper upload output/MyApp.ipa \
              --notes "$(git log -1 --pretty=%B)" \
              --tag adhoc

macOS executors have Node preinstalled, so nothing extra is needed there.

Upload only on some branches

The cleanest way is a workflow-level filter, so the job doesn’t even start on branches you don’t distribute:

.circleci/config.yml
workflows:
  beta:
    jobs:
      - build-android:
          context: appdropper
          filters:
            branches:
              only:
                - main
                - develop

To keep one job and skip only the upload, CircleCI sets $CIRCLE_BRANCH:

.circleci/config.yml
- run:
    name: Upload to App Dropper
    command: |
      if [ "$CIRCLE_BRANCH" = "main" ]; then
        npx appdropper upload app/build/outputs/apk/release/app-release.apk \
          --notes "$(git log -1 --pretty=%B)"
      else
        echo "Skipping upload on $CIRCLE_BRANCH"
      fi

Using the install link

The CLI prints the install URL, and only the install URL, on stdout. CircleCI shares values between steps through $BASH_ENV:

.circleci/config.yml
- run:
    name: Upload to App Dropper
    command: |
      INSTALL_URL=$(npx appdropper upload app/build/outputs/apk/release/app-release.apk)
      echo "export INSTALL_URL='$INSTALL_URL'" >> "$BASH_ENV"
      echo "Install: $INSTALL_URL"

- run:
    name: Tell the team
    command: |
      curl -X POST -H 'Content-type: application/json' \
        --data "{\"text\":\"New beta build: $INSTALL_URL\"}" \
        "$SLACK_WEBHOOK_URL"

JSON output

For anything more involved than a link, --json gives you the whole result — version, build number, platform, bundle ID, install URL, QR URL and expiry:

.circleci/config.yml
- run:
    name: Upload to App Dropper
    command: |
      npx appdropper upload app/build/outputs/apk/release/app-release.apk --json > result.json
      VERSION=$(jq -r .version result.json)
      URL=$(jq -r .install_url result.json)
      echo "Uploaded $VERSION → $URL"

- store_artifacts:
    path: result.json

Troubleshooting

“No API token”

If the variable lives in a context, the jobhas to declare that context in the workflow — a context isn’t inherited automatically. If it’s a project variable, note that CircleCI withholds project variables from forked-PR builds unless you’ve explicitly enabled “Pass secrets to builds from forked pull requests”.

“npx: command not found”

The executor image has no Node. Use a -node image variant, or add node/install from the Node orb before the upload step.

The upload runs on every branch

Branch filters go under the job entry in the workflows section, not on the job definition itself — a filter in the wrong place is silently ignored.

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