How to share a React Native build with testers
· 6 min read
Getting a React Native build onto a tester's phone is two separate jobs: a release APK from Gradle for Android, and a signed IPA from Xcode for iPhone. Neither is hard, but each has one step that trips people up. On Android it is signing. On iOS it is the export method. This guide walks through both, plus the Expo EAS route, and ends with a single link your testers can open on either platform.
Debug builds are not test builds
The build you run with npx react-native run-android or from Xcode in Debug loads its JavaScript from Metro on your machine. Send that to a tester and the app opens to a red screen that says it is unable to load the script, because there is no Metro server on their network. A shareable build has to be a release build, where the JavaScript bundle (and Hermes bytecode, if you use Hermes) is packed inside the app.
Android: a release APK with Gradle
Build it
cd android
./gradlew assembleRelease
The file lands at android/app/build/outputs/apk/release/app-release.apk. That APK is what you share. The other common task, ./gradlew bundleRelease, makes an .aab for Google Play. Phones cannot install an app bundle directly, so it is not useful for testers outside Play.
Check the signing config
The React Native template signs the release build type with the debug keystore so that assembleRelease works out of the box. That APK installs, but:
- Google Play rejects debug signed uploads, so you will need a real key anyway.
- If a tester has a copy signed with one key and you send an update signed with another, Android refuses the update until they uninstall.
Set up a release keystore once in android/app/build.gradle, keep the passwords out of the repository (for example in ~/.gradle/gradle.properties or CI secrets), and sign every test build with it. Android app signing for APK, AAB and sideloading covers the key types in more detail.
Version every build
Bump versionCode and versionName in android/app/build.gradle before each round. Android will not install a build with a lower versionCode over a higher one, and testers need a way to tell builds apart.
iOS: archive and export for testing
Register test devices first
An iPhone installs a build from a link only when it is signed Ad Hoc, Development or Enterprise. For outside testers that usually means Ad Hoc, which installs only on devices whose UDID is in the provisioning profile. Collect UDIDs with the free UDID finder, add them in the Apple Developer portal, and regenerate the profile. Registering a UDID explains the steps.
From Xcode
- Open
ios/YourApp.xcworkspace(the workspace, not the project, because of CocoaPods). - Pick "Any iOS Device" as the destination and choose Product, Archive. The scheme's archive action uses the Release configuration by default.
- In the Organizer, choose Distribute App and pick Release Testing. That is the current Xcode name for Ad Hoc distribution. "Debugging" is the old Development option.
- Export, and you get a folder with the
.ipa.
From the command line
For CI or repeatable builds, use xcodebuild with an export options file:
xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp \
-configuration Release -destination "generic/platform=iOS" \
-archivePath build/YourApp.xcarchive archive
xcodebuild -exportArchive -archivePath build/YourApp.xcarchive \
-exportOptionsPlist ios/ExportOptions.plist -exportPath build/ipa
In ExportOptions.plist, set method to release-testing and teamID to your team id. Xcode 15.4 renamed the old ad-hoc value, and the old name now gives a deprecation warning. For manual signing, add signingStyle set to manual and a provisioningProfiles entry that maps your bundle id to the Ad Hoc profile name.
Inspect before you share
The IPA inspector reads the IPA in your browser, without uploading it, and shows the distribution type, profile expiry and the provisioned UDIDs. Thirty seconds there saves a round of "it says unable to install" messages.
The Expo EAS alternative
If your project uses Expo, EAS Build has its own way to make test builds. In eas.json, a build profile with "distribution": "internal" produces builds meant for direct install:
{
"build": {
"preview": {
"distribution": "internal",
"android": { "buildType": "apk" }
}
}
}
Run eas build --profile preview --platform all. On Android, buildType: "apk" makes an installable APK instead of an app bundle. On iOS, EAS signs with an Ad Hoc profile and registers devices through eas device:create, so the same UDID rule applies. EAS gives each build its own URL.
Many teams stop there, and that is fine. Teams that build some apps outside EAS, or want one place for every project's builds with passwords, release notes and the same install page on both platforms, download the EAS artifact and upload it to AppForShare. Both approaches use the same underlying signing, so pick based on where you already build.
Share the build
With an APK and an Ad Hoc IPA in hand:
- Upload each file to AppForShare from the home page or the dashboard.
- Send testers the install link or show the QR code.
- They open it in the phone's browser and install over the air.
Android testers allow installs from unknown sources for their browser once; the phone asks for it the first time. From a pipeline, the upload is one line after the build:
cd android && ./gradlew assembleRelease && cd ..
npx --yes appforshare@1 upload android/app/build/outputs/apk/release/app-release.apk --notes "New onboarding flow"
The CLI reads an API token from APPFORSHARE_TOKEN (create one under Dashboard, Settings on Pro and Team). Uploading builds from CI has examples for GitHub Actions, GitLab, Bitrise and others.
Common React Native install problems
| What the tester sees | Likely cause | Fix |
|---|---|---|
| Red screen about loading the script | Debug build shared | Build with assembleRelease or the Release configuration |
| Android: "App not installed" | Existing copy signed with another key, or a lower versionCode | Uninstall the old copy, sign every build with the same key, bump versionCode |
| iPhone: "Unable to install" or "integrity could not be verified" | UDID not in the profile, expired profile, or App Store export | Register the device, regenerate the profile, export with Release Testing |
| App opens but shows no data | Release build still points at a local API address | Use a reachable test backend in the release config |
| iPhone asks for Developer Mode | Development signed build | Turn on Developer Mode in Settings, Privacy and Security, or switch to Ad Hoc |
One more cause of the "no data" case: release builds on Android block plain http:// traffic by default, so a test backend without HTTPS can fail only in the release build. More iOS causes are in can't install an app on an iOS device.
A routine that works
- Every merge to your main branch builds a release APK and an Ad Hoc IPA.
- Both go to the same AppForShare project with the commit message as release notes.
- New testers send their UDID first; you rebuild the IPA once their device is in the profile.
- Store builds still go through TestFlight and Google Play testing tracks before release.
Send your next React Native build
AppForShare turns each .apk and .ipa into an install link and QR code, with the same page for Android and iPhone testers. Account uploads also get a store readiness check, which is guidance rather than a promise of approval, and a comparison with the previous build. Start on the Free plan with 7 day links, or move to Pro at $12 a month for CI uploads, release notes and links that stay live while you are subscribed. Sign up free and share your build.


