Hands-off Releases
Tag a release in Git and your signed iOS and Android binaries are submitted to TestFlight and Play Store automatically.
Copy a setup prompt with the install steps and the full markdown guide for this plugin.
Automate your iOS and Android builds directly from your GitHub repository. With one workflow file and a handful of repository secrets, every push, tag, or manual trigger can produce signed, store-ready apps â without anyone on the team needing a Mac, Xcode, or Android Studio installed.
Hands-off Releases
Tag a release in Git and your signed iOS and Android binaries are submitted to TestFlight and Play Store automatically.
No Local Setup
Contributors on Windows or Linux can trigger iOS builds. No Xcode, no provisioning hassles, no shared signing certificates floating around laptops.
Scoped Secrets
Credentials live in GitHub repository secrets, scoped to your repo and visible only to the workflow runner. Easy to rotate, easy to audit.
Parallel Builds
Build iOS and Android at the same time with a matrix job. A typical release finishes in under 10 minutes.
Before setting up the workflow, make sure you have:
bunx @capgo/cli@latest app add if not)bunx @capgo/cli@latest build init â see Managing Credentials for the wizard walkthroughbunx @capgo/cli@latest build com.example.app --platform android --build-mode debug) â CI is not the place to debug your first buildgh) installed and authenticated (gh auth login)The Capgo CLI can export your local credentials as a ready-to-use .env file. Combined with gh secret set -f, this turns the entire CI/CD setup into three commands â no manual base64 encoding, no JSON wrangling, no copy-paste-secret-by-secret.
Add your Capgo API key as a repository secret
The API key isnât part of the per-app credential store, so add it once manually:
gh secret set CAPGO_TOKEN --body "your_capgo_api_key_here"Generate the key in the Capgo dashboard with upload permissions or higher.
Export your credentials to a .env file
Run the interactive credentials manager:
bunx @capgo/cli@latest build credentials manage --appId com.example.appIn the TUI, select Export to .env. The CLI writes .env.capgo.<appId> to your current directory with mode 0600 (owner-readable only) â for example, .env.capgo.com.example.app. When both iOS and Android are configured, both platformsâ secrets land in the same file under # === IOS === and # === ANDROID === section headers. iOS and Android env-var names are disjoint, so combining them is conflict-free.
Push the .env file to GitHub Actions secrets
The gh secret set -f command reads a dotenv file and creates one repository secret per KEY=value line:
gh secret set -f .env.capgo.com.example.appThatâs it â every secret your workflow needs is now in GitHub. Verify with gh secret list.
Create the workflow file
Add .github/workflows/capgo-build.yml to your repository. Pick one of the three trigger patterns below depending on how you want to fire builds.
For reference, gh secret set -f will create these repository secrets (your workflow YAML references them by these exact names):
| Platform | Secrets created |
|---|---|
| iOS | BUILD_CERTIFICATE_BASE64, P12_PASSWORD, CAPGO_IOS_PROVISIONING_MAP_BASE64, APPLE_KEY_ID, APPLE_ISSUER_ID, APPLE_KEY_CONTENT, APP_STORE_CONNECT_TEAM_ID |
| Android | ANDROID_KEYSTORE_FILE, KEYSTORE_KEY_ALIAS, KEYSTORE_KEY_PASSWORD, KEYSTORE_STORE_PASSWORD, PLAY_CONFIG_JSON |
| (added manually) | CAPGO_TOKEN |
You donât need to memorise these â the workflow examples below already reference all of them.
The three examples below cover the most common patterns. They all use the same shape: check out the repo, install dependencies, build the web assets, sync to native, then call Capgo Build with credentials passed as environment variables.
Lets anyone with write access fire a build from the Actions tab in GitHub with a platform dropdown. Useful for ad-hoc test builds or kicking off a release on demand.
name: Capgo Build (Manual)
on: workflow_dispatch: inputs: platform: description: 'Platform to build' required: true default: 'android' type: choice options: [ios, android, both] mode: description: 'Build mode' required: true default: 'debug' type: choice options: [debug, release]
jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: bun-version: latest
- run: bun install --frozen-lockfile - run: bun run build - run: bunx cap sync
- name: Trigger Capgo Build env: CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} CAPGO_IOS_PROVISIONING_MAP_BASE64: ${{ secrets.CAPGO_IOS_PROVISIONING_MAP_BASE64 }} APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }} APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }} ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }} KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} PLAY_CONFIG_JSON: ${{ secrets.PLAY_CONFIG_JSON }} run: | bunx @capgo/cli@latest build com.example.app \ --platform ${{ inputs.platform }} \ --build-mode ${{ inputs.mode }}Replace com.example.app with your app ID. Once committed, go to Actions â Capgo Build (Manual) â Run workflow to trigger it.
Builds and ships both platforms in parallel whenever you push a version tag like v1.4.0. This is the most common production setup â git tag v1.4.0 && git push --tags becomes your release command.
name: Capgo Build (Release)
on: push: tags: - 'v*'
jobs: build: runs-on: ubuntu-latest strategy: fail-fast: false matrix: platform: [ios, android] steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: bun-version: latest
- run: bun install --frozen-lockfile - run: bun run build - run: bunx cap sync ${{ matrix.platform }}
- name: Build ${{ matrix.platform }} env: CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} # iOS BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} CAPGO_IOS_PROVISIONING_MAP_BASE64: ${{ secrets.CAPGO_IOS_PROVISIONING_MAP_BASE64 }} APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }} APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }} APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }} # Android ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }} KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} PLAY_CONFIG_JSON: ${{ secrets.PLAY_CONFIG_JSON }} run: | bunx @capgo/cli@latest build com.example.app \ --platform ${{ matrix.platform }} \ --build-mode releaseThe matrix runs iOS and Android in parallel on separate runners. Setting fail-fast: false means a failed iOS build wonât cancel the in-progress Android build (and vice versa) â useful when one platform has a transient signing issue.
Catches native build regressions early by producing a debug Android build on every push to main. Cheap to run, fast feedback, and you can skip Play Store upload to keep it purely a smoke test.
name: Capgo Build (Main)
on: push: branches: [main] paths: - 'src/**' - 'android/**' - 'ios/**' - 'package.json' - 'capacitor.config.*'
jobs: smoke-build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 with: bun-version: latest
- run: bun install --frozen-lockfile - run: bun run build - run: bunx cap sync android
- name: Smoke build (Android debug) env: CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} ANDROID_KEYSTORE_FILE: ${{ secrets.ANDROID_KEYSTORE_FILE }} KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} run: | bunx @capgo/cli@latest build com.example.app \ --platform android \ --build-mode debug \ --no-playstore-upload \ --output-uploadThe paths filter ensures the workflow doesnât run on doc-only changes. --no-playstore-upload skips Play Store submission (no PLAY_CONFIG_JSON needed), and --output-upload produces a download URL for the resulting APK so you can install it on a test device.
For test builds, use --no-playstore-upload (Android) or --no-app-store-upload (iOS) to skip store submission. Combine with --output-upload to get a time-limited download URL for the binary.
Pass --output-record <path> to persist the build artifact URL and QR code to disk when the build succeeds, then read it back in subsequent steps with build last-output. No log scraping, no regex.
- name: Build env: CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} # ...credentials... run: | bunx @capgo/cli@latest build com.example.app \ --platform android --build-mode debug \ --output-upload --output-retention 1d \ --output-record /tmp/build.json
- name: Comment on PR with build URL env: GH_TOKEN: ${{ github.token }} run: | URL=$(bunx @capgo/cli@latest build last-output --path /tmp/build.json --field outputUrl) if [ -n "$URL" ]; then gh pr comment ${{ github.event.pull_request.number }} \ --body "Debug build ready: $URL" fi--output-record /tmp/build.json writes a JSON record (with jobId, status, outputUrl, qrCodeAscii, qrCodePngPath, finishedAt) and a PNG QR code alongside at /tmp/build.json.qr.png. build last-output reads it back:
--field outputUrl prints just the download URL (newline-terminated; safe for URL=$(...)).--field qrCodePngPath prints the PNG path so you can upload it as a PR attachment.--qr prints the rendered ASCII QR â drop it inside a Markdown code fence on the PR comment for inline scannability.By default each release build increments the build number. To pin it to a value you control (for example, the Git tag), pass --skip-build-number-bump:
- name: Set version from tag run: | VERSION="${GITHUB_REF#refs/tags/v}" # Update package.json or your version source here bun pm version "$VERSION" --no-git-tag-version
- name: Build env: CAPGO_TOKEN: ${{ secrets.CAPGO_TOKEN }} # ...credentials... run: | bunx @capgo/cli@latest build com.example.app \ --platform ios --build-mode release \ --skip-build-number-bumpbun install is already fast enough that a JS-deps cache rarely pays off, but Capacitorâs native dependencies (CocoaPods, Gradle) are worth caching for larger projects:
- uses: actions/cache@v4 with: path: | ~/.bun/install/cache ios/App/Pods android/.gradle key: ${{ runner.os }}-capgo-${{ hashFiles('**/bun.lock', '**/Podfile.lock') }}| Symptom | Likely cause |
|---|---|
CAPGO_TOKEN is not set | Secret not added, or job has no access to it (check environment/branch protections) |
| Missing iOS / Android credential errors | gh secret set -f was not run, or was run against a different repo. Verify with gh secret list |
cap sync fails in CI but works locally | A native plugin isnât in package.json, or you forgot bun install before cap sync |
| Build succeeds but no app appears in App Store Connect | Wrong team ID, or the app record doesnât exist yet in App Store Connect. Verify locally with bunx @capgo/cli build credentials manage |
| Build hangs after âUploading projectâ | Project archive is unusually large â check that node_modules isnât being uploaded (it shouldnât be by default) |
Provisioning profile doesn't match bundle ID | The provisioning map points at a different bundle ID than the one Xcode is signing. Re-run build init to refresh the profile, then re-export with build credentials manage |
| Credentials changed locally but CI still fails | Donât forget to re-export and re-push: bunx @capgo/cli build credentials manage â gh secret set -f .env.capgo.<appId> |
| Manager refuses to write the combined file | Shared config keys differ between platforms â the manager warns and asks for confirmation. Either confirm to overwrite-one-wins, or re-export per-platform with --platform ios / --platform android |
build last-output prints an empty URL | The build did not pass --output-upload, or it failed before producing an artifact. outputUrl will be null in the record. Branch on [ -n "$URL" ] before using it |
build last-output errors with Unsupported record schemaVersion | The runner is on an older CLI than the one that wrote the record. Pin both producer and reader to the same explicit version (e.g. bunx @capgo/cli@7.104.0 ⌠on both sides) rather than @latest, which floats and can drift between jobs |
For platform-specific build failures, see the Troubleshooting guide.