PUBLISH A BUILD FROM YOUR TERMINAL OR CI IN ONE COMMAND
One command uploads a signed .ipa or .apk and prints an install link testers open on their phone.
The same command runs on your Mac and inside GitHub Actions, fastlane, GitLab CI, Bitrise, or Jenkins.
BetaDrop CLI demo transcript:
npm install -g @betadrop/cli
bd login — log in via browser or API token
bd publish ./MyApp.ipa
Success! Build is live. Public link: https://betadrop.app/install/?i=abc123 — scan the QR code to install on your device.
From install to your first published build in under 2 minutes.
Choose your preferred installation method. The recommended installer scripts automatically verify requirements.
curl -fsSL https://betadrop.app/install.sh | bashiwr -useb https://betadrop.app/install.ps1 | iexnpm install -g @betadrop/clipnpm is only required if building the CLI from source.Authenticate via your browser, or use an API token. On a CI runner you skip login entirely: set BETADROP_TOKEN in the environment and publish reads it.
betadrop loginbetadrop login --token bd_live_xxxxxxxxxxxxxxxxxxxxBETADROP_TOKEN=bd_live_xxxxxxxxxxxxxxxxxxxx betadrop publish ./MyApp.apk --ciPoint betadrop publish at your IPA or APK. An install link and QR code are printed instantly.
betadrop publish ./MyApp.ipaAll commands below are shown using the betadrop command, but the short alias bd can be used interchangeably.
betadrop loginbetadrop login --token bd_live_…betadrop publish <file>betadrop publish <file> --cibetadrop publish <file> --jsonbetadrop whoamibetadrop logoutpublish takes a path to a signed build and four flags. Everything else is read out of the binary itself: app name, version, bundle identifier, and whether it is an iOS or an Android build. There is nothing to configure and no project file to keep in sync.
--ci--json--name <name>--notes <notes>Files are checked before they are uploaded: the CLI reads the first four bytes and rejects anything that is not really a ZIP archive, so a renamed file fails in about a second instead of after the whole transfer. Only .ipa and .apk are accepted. BetaDrop does not distribute .aab and never re-signs a build. Uploads obey the same limits as the web uploader: up to 500 MB per build on Free and Pro, 1 GB on Studio, links live 3 days by default, and a free account can extend a link to 7 days.
The CLI is the whole CI integration. Even the packaged GitHub Action is a thin wrapper around it. There is no agent to install and no plugin to maintain: a runner installs the npm package and runs one command. In CI, betadrop publish reads its token from the BETADROP_TOKEN environment variable, so nothing interactive ever runs and betadrop login never has to execute on a build machine. Add the step after your build step, once the artifact actually exists on disk.
The packaged Action, on the Marketplace as BetaDrop Upload, wraps the CLI in one step: it installs the package, uploads the build, and exposes the link as an install-url output later steps can post to a pull request comment, a Slack channel, or the job summary.
- name: Publish to BetaDrop
id: betadrop
uses: betadrop-app/upload-action@v1
with:
file: app/build/outputs/apk/release/*.apk
token: ${{ secrets.BETADROP_TOKEN }}
Pin @v1, never @main. Calling the CLI directly in a run: step publishes identically. The generic snippet below is that version, and it keeps working if you ever move off GitHub. Full Android and iOS workflows, including certificate import on macOS runners →
If building and signing already live in your Fastfile, distribution is one more line in the lane. sh() returns standard output, and with --ci standard output is exactly the install URL. The lane that produced the binary captures the link and can hand it to Slack. The same shape covers gym on iOS and gradle on Android. Complete beta lanes for both platforms →
Nothing above is GitHub-specific. Any runner that can run Node 18 or newer runs the same two lines, in whatever a script step is called on your system.
npm install -g @betadrop/cli
betadrop publish ./build/MyApp.ipa --ci --name "MyApp build $BUILD_NUMBER"
Store the token in the secret store each system already has (a masked CI/CD variable on GitLab, a secret env var on Bitrise, a credentials binding on Jenkins), and expose it to the step as BETADROP_TOKEN so it stays out of the repository and out of the log.
Three of those now have their own guide, with the real artifact paths and secret handling for each: GitLab CI, Bitrise and Codemagic. See every guide on the integrations hub, including Flutter, React Native, Expo, and Unity build paths.
A URL sitting in a build log helps nobody. Once the publish step has captured it, spend it somewhere a tester will actually look: a comment on the pull request that produced the build, a message in the channel where QA lives, the run summary your team already opens, or a row in the release checklist. Testers need nothing installed. They open the link on the phone, or scan the QR code from the install page, and the build installs from the browser.
Every publish creates a new build with its own link, so the link you sent yesterday keeps working and nothing is silently overwritten. That is worth knowing before you wire a pipeline that runs on every push: use --name with a run number or branch so the list stays readable, and remember one runner constraint the CLI cannot fix — an iOS archive has to be built and signed on macOS, so the iOS job needs a macOS runner even though the publish step itself runs anywhere.
A CLI token is an account credential. It uploads builds as you, so treat it the way you treat a deploy key.
Create tokens at betadrop.app under Settings → Developer → API tokens. They begin with bd_live_ and are shown once.
BETADROP_TOKEN is set, the CLI uses it and never reads or writes the local credentials file. That is what makes a shared runner safe to publish from: nothing is left on the machine when the job ends.betadrop login writes the token to your home directory with owner-only permissions. betadrop whoami tells you which token is active, its name, expiry, and when it was last used — the quickest way to spot a token nobody has used in months.betadrop logout revokes the stored token server-side, not just on the machine you ran it on. A token you supplied through the environment is revoked from the dashboard instead. Keep a token out of echo and set -x output and it never reaches your build log at all.If you cut a tester build by hand every week or two, the CLI is more setup than the job needs. Installing a global npm package and minting a token takes longer than the upload itself. Drag the file onto upload an IPA or upload an APK and you have the same link and QR code — guest upload works with no account at all.
The same applies on a machine that is not yours, or when you are sharing a build someone else produced and never touch the build system. The CLI earns its place when publishing repeats: every push, every lane run, every nightly, every time a build should reach testers without a human remembering to send it.
And if your work already happens in an editor conversation, the BetaDrop MCP server does the same job without a terminal: Claude Code, Cursor, or Copilot uploads the build and hands back the install link. One thing no tool changes: Apple's signing rules still apply. An ad-hoc iOS build installs only on devices whose UDID is in the provisioning profile, and enterprise builds still need the one-time trust step on the device.
Add one step to the job that already builds your app, after the artifact exists. The step runs `npm install -g @betadrop/cli`, then `betadrop publish <path-to-artifact> --ci`, with BETADROP_TOKEN passed in from `secrets`. Because --ci prints only the install URL, you can capture it into $GITHUB_OUTPUT and reuse it in a PR comment, a Slack message, or the job summary. Prefer a single step? The packaged Action — `uses: betadrop-app/upload-action@v1`, on the Marketplace as BetaDrop Upload — wraps this exact CLI call and sets the install-url output for you.
In CI the BetaDrop CLI reads its token from the BETADROP_TOKEN environment variable. When that variable is set, the CLI uses it and never reads or writes the local credentials file, so `betadrop login` never has to run on a runner. Generate the token at betadrop.app under Settings → Developer → API tokens, store it in your CI system's secret store, and expose it to the publish step as an environment variable — never paste it into the workflow file itself.
Yes. Nothing about the CLI is GitHub-specific: any runner that can run Node 18 or newer can install the package and run `betadrop publish`. In fastlane, wrap it in sh() inside a lane and capture the returned URL. In GitLab CI, Bitrise, and Jenkins, it is the same two lines in a script step, with BETADROP_TOKEN supplied by that system's secret mechanism (masked CI/CD variable, secret env var, and credentials binding respectively).
It switches publish into non-interactive output: no progress bar, no QR block, no summary. Standard output becomes exactly one line, the install URL, so `url="$(betadrop publish app.apk --ci)"` captures a usable link with no parsing. If a script needs more than the link, use --json instead, which prints the build id, short id, and install URL as JSON.
The CLI can neither upload an AAB nor sign a build for you. `betadrop publish` accepts .ipa and .apk only, and it checks the file's magic bytes before uploading, so a renamed or wrong-format file fails in about a second instead of after the whole upload. BetaDrop also never re-signs anything: an iOS build must already be signed, and it installs only on devices covered by its provisioning profile. Build and sign in Xcode, fastlane, or your CI, then publish the result.
Install the CLI, log in once, and publish your first build in under 2 minutes.
npm install -g @betadrop/cli