CI/CD Overview
For CI/CD, the main difference is authentication.
Instead of relying on a local interactive CLI session, you usually pass a Personal Access Token with --token.
Why --token Exists
CI environments do not go through bundle-drop login, so token-based auth is the normal approach for automated uploads.
Typical CI Upload
For Native Fingerprinting, install the same locked dependencies and use the same native configuration and relevant environment as the binary build. Upload calculates compatibility automatically; no fingerprint override flag is required. Explicit platform values can remain as hybrid runtime boundaries.
npx bundle-drop upload android \
--version 1.2.3 \
--channel develop \
--token $BUNDLE_DROP_PAT \
--author "CI Pipeline"For checked-in multiline Markdown notes, add --release-notes-file ./release-notes.md. The path is relative to the invoking project directory. Do not pass --release-notes in the same command. The CLI rejects unreadable or empty files before bundling and warns before truncating notes beyond 2,000 characters.
Using --token with Other Commands
You can also authenticate setup with a token instead of browser login. Guided setup can still require context-sharing consent and interactive review:
npx bundle-drop init --token bdp_pat_xxxArtifacts and the Result File
When you pass --artifact-dir, Bundle Drop writes a bundle-drop-result.json file into that directory after a successful upload. A failed upload can also write a diagnostic result with success: false. Always check the command's exit status before using the result as a successful release.
A successful result contains metadata about the uploaded bundle:
{
"success": true,
"projectId": "8e71cf16-4a8e-44d2-93aa-d2a822cb5fa1",
"channelId": "c58eaab7-2225-4da9-b379-c8d753f9460a",
"serverUrl": "https://api.bundledrop.app",
"platform": "android",
"appVersion": "4.7.3",
"channel": "develop",
"orgSlug": "my-org",
"projectSlug": "my-app",
"runtimeVersion": "4.0.9",
"packageVersion": "0.12.0",
"bundleId": "...",
"bundleVersion": 339,
"bundleVersionLabel": "v339",
"hash": "a970a7a5f65b9fa5ff645386d4b3cbc1034a8d7d...",
"releaseNotes": "Fix login screen crash",
"author": "CI Pipeline",
"zipSizeBytes": 1834201,
"bundlingAndPackagingMs": 12435,
"uploadMs": 1832,
"totalMs": 14892,
"dashboardUrl": "https://developer.bundledrop.app/organizations/my-org/projects/my-app",
"bundlePath": "/artifacts/bundledrop/android/main.jsbundle",
"sourceMapPath": "/artifacts/bundledrop/android/main.jsbundle.map"
}Note that packageVersion is the installed @gfean/react-native-bundle-drop SDK version, while appVersion is your app's version — they are different values.
SDK 0.12.0 adds success, projectId, channelId, and serverUrl to the saved upload result. Use projectId, channelId, and hash to identify the uploaded release in subsequent Public API calls. serverUrl is the API base URL actually used for the upload. Project and channel IDs can be null when an older server does not return them; validate that they are present before making API calls. If you use an older SDK, you can supply fixed project and channel IDs from your CI secret store alongside the PAT instead of reading them from the result file.
zipSizeBytes is measured from the uploaded archive. The timing fields report elapsed milliseconds for bundling and packaging together, the upload request, and the whole command. dashboardUrl links to the project page for the exact production or staging API; it is null for custom servers. The console summary shows these values after upload and does not claim the bundle is already eligible for delivery. If local artifact reporting fails after the server confirms upload, the CLI warns; inspect the server before retrying to avoid a duplicate upload.
You can read any field from this file with jq to use in later pipeline steps — setting variables, uploading source maps, posting notifications, tagging releases, etc.
Full CI Example with Artifact Variables
This Bash function uploads a bare React Native bundle, retains artifacts, and extracts values for later steps. Supply the variables from your CI environment and secret store. It works with GitHub Actions, Azure Pipelines, GitLab CI, Bitrise, or another runner with Node.js and jq. Expo projects use the option-free platform commands shown in Uploading.
deploy_bundledrop() {
PLATFORM_NAME=$1
PLATFORM_LC=$(echo "$PLATFORM_NAME" | tr '[:upper:]' '[:lower:]')
ARTIFACT_DIR="$STAGING_DIR/bundledrop/$PLATFORM_LC"
mkdir -p "$ARTIFACT_DIR" || return 1
echo "Releasing OTA for $PLATFORM_NAME to channel $CHANNEL..."
if [ "$PLATFORM_LC" = "ios" ]; then
npx bundle-drop upload ios \
--plist-file ios/MyApp/Info.plist \
--channel "$CHANNEL" \
--token "$BUNDLE_DROP_TOKEN" \
--release-notes "$RELEASE_NOTES" \
--author "$BUILD_REQUESTER" \
--sourcemap \
--artifact-dir "$ARTIFACT_DIR" || return $?
elif [ "$PLATFORM_LC" = "android" ]; then
npx bundle-drop upload android \
--buildgradle-path android/app/build.gradle \
--channel "$CHANNEL" \
--token "$BUNDLE_DROP_TOKEN" \
--release-notes "$RELEASE_NOTES" \
--author "$BUILD_REQUESTER" \
--sourcemap \
--artifact-dir "$ARTIFACT_DIR" || return $?
else
echo "Expected ios or android, got: $PLATFORM_NAME" >&2
return 1
fi
BD_RESULT="$ARTIFACT_DIR/bundle-drop-result.json"
if [ ! -f "$BD_RESULT" ]; then
echo "Result file not found at $BD_RESULT"
return 1
fi
if ! jq -e '.success != false and .status != "failed" and (.hash | type == "string" and length > 0) and (.bundleVersion | type == "number")' "$BD_RESULT" > /dev/null; then
echo "Upload result does not confirm a successful bundle" >&2
return 1
fi
# Extract values for downstream steps
BD_PROJECT_ID=$(jq -r '.projectId // empty' "$BD_RESULT")
BD_CHANNEL_ID=$(jq -r '.channelId // empty' "$BD_RESULT")
BD_SERVER_URL=$(jq -r '.serverUrl // empty' "$BD_RESULT")
BD_HASH=$(jq -r '.hash // empty' "$BD_RESULT")
BD_VERSION=$(jq -r '.bundleVersion // empty' "$BD_RESULT")
BD_LABEL=$(jq -r '.bundleVersionLabel // empty' "$BD_RESULT")
BD_APP_VERSION=$(jq -r '.appVersion // empty' "$BD_RESULT")
BD_RUNTIME=$(jq -r '.runtimeVersion // empty' "$BD_RESULT")
BD_BUNDLE=$(jq -r '.bundlePath // empty' "$BD_RESULT")
BD_SOURCEMAP=$(jq -r '.sourceMapPath // empty' "$BD_RESULT")
echo "Uploaded $PLATFORM_NAME bundle v$BD_VERSION ($BD_LABEL) — hash: $BD_HASH"
}
# Stop the pipeline if upload fails; do not run downstream artifact steps.
deploy_bundledrop android || exit $?Source Map Upload for Error Tracking
After extracting the hash, bundle path, and source-map path from the result
file, upload the matching artifacts to your error tracking provider using the
hash as the dist identifier:
# Sentry
sentry-cli sourcemaps upload \
--release "$RELEASE" \
--dist "$BD_HASH" \
--bundle "$BD_BUNDLE" \
--bundle-sourcemap "$BD_SOURCEMAP" \
--validate
# Bugsnag
bugsnag-sourcemaps upload \
--source-map "$BD_SOURCEMAP" \
--code-bundle-id "$BD_HASH" \
--app-version "$BD_APP_VERSION"
# Datadog
datadog-ci sourcemaps upload "$BD_SOURCEMAP" \
--service my-app \
--release-version "$BD_APP_VERSION" \
--minified-path-prefix /Without --sourcemap, the upload works exactly as before — no source map is generated, and artifacts are cleaned up after upload. See Observability for runtime integration details.
If the app uses @sentry/react-native 7.8.x with Hermes, apply the one-time
Sentry and Hermes OTA Metro configuration
so content-derived Debug IDs do not destabilize binary patch sizes.
What the Client Needs to Know
- use
bundle-drop loginfor local development - use
--tokenfor automated environments - keep the token in your CI secret store, not in source control
Expo projects use the same token flow and option-free platform uploads. If the project opts into strict Expo fingerprint authority, import the exact local or EAS build receipt before uploading. See Expo.
Related Docs
- For general CLI setup, see CLI.
- For upload command details, see Uploading.
- For error tracking integration, see Observability.