AppForShare

How to test a CodePush or EAS Update before your users get it

· 9 min read

How to test a CodePush or EAS Update before your users get it

An over the air code update skips the store. That is the point of it, and also the risk: a JavaScript change you push to Production reaches every matching install within minutes, with no reviewer and no staged store release in between. If the update breaks the checkout screen, your users find out before you do.

This guide is about testing an update before your users get it. It covers why an update only runs on a matching binary, how to give testers that binary, a staging loop for CodePush style services and Expo EAS Update, a checklist, and what the store rules say about code updates. If you are not sure whether you need a code update or a new build, start with OTA install vs OTA code updates.

One note before we start: AppForShare, the tool behind this blog, does not offer a code update service. It shares test builds. The tools named below are the ones teams use for the update itself.

The tools, as of November 2026

Since Visual Studio App Center was retired on March 31, 2025, React Native teams have spread across several services. As read on their sites on November 3, 2026:

  • Expo EAS Update: Expo's hosted update service for Expo and bare React Native apps with expo-updates. Builds read a channel, updates are published to branches, and a runtime version decides which builds can run an update.
  • AppsOnAir (by Logicwind) CodePush: a hosted service for React Native JavaScript and asset updates, presented as a drop in for App Center CodePush, with its own CLI (@appsonair/codepush-cli). Its free plan lists 10 releases and 1,000 bundle downloads a month; paid plans raise both.
  • RevoPush: a hosted React Native update service that works with the open source CodePush client SDK, supports React Native 0.76 and later, and lists gradual rollouts, rollback and diff updates.
  • Bitrise CodePush: a hosted service inside Bitrise Release Management for React Native and Expo apps, using the open source CodePush client SDK.
  • Self hosted CodePush server: Microsoft's standalone server, archived in May 2025. You run the backend yourself.

The names differ, but the testing problem is the same on all of them.

The binary and the bundle

A React Native app has two layers. The native layer is compiled into the binary you build and sign: native modules, permissions, the React Native and Hermes versions, the app icon. The JavaScript bundle and assets sit on top and can be swapped. An update replaces only the top layer, so it can only run on a binary whose native layer it was built for.

Each tool has a way to say which binaries an update is for:

  • CodePush style services use a target binary version. Microsoft's CLI docs say that if you leave it out, it targets the exact version in your Info.plist and build.gradle. Ranges are allowed, which is convenient and also how an update reaches a binary it was never tested on.
  • EAS Update uses runtimeVersion. Expo's docs describe it as "a property that guarantees compatibility between a build's native code and an update", with policies such as appVersion and fingerprint, which changes whenever anything native changes.

The practical rule: you can only test an update properly on the same binary your users have, or one built from the same native code. Testing it in a debug build on a simulator tells you the JavaScript works. It does not tell you the update applies to the release binary.

Give testers the staging binary

The update client inside the binary decides where it looks for updates. In CodePush style services that is a deployment key compiled into the build; every new app gets a Staging and a Production deployment. In EAS it is the channel set in the build profile. So you need two binaries built from the same native code:

  • A store binary that reads Production (or the production channel).
  • A staging binary, release configuration, same version and native code, that reads Staging (or a preview channel). This is the one testers install.

Build the staging binary as a release build, not a debug build. Debug builds load JavaScript from the packager and do not exercise the update client at all. On iOS, sign it Ad Hoc so it installs from a link on registered devices; on Android, sign it with the same upload key you use for test builds.

Then share it the way you share any test build. On AppForShare, upload it and put it into a channel such as Staging. Testers get one link and QR code that always opens the newest staging binary, so when the native layer changes and you build a new binary, the link stays the same. Write in the release notes which update channel the build reads, so nobody confuses it with the store build.

The staging loop

  1. Release the update to Staging only. Target the exact binary version the testers have.
  2. Testers restart the app. Most update clients download in the background and apply on the next start, unless the update is marked mandatory or your code applies it at once. Tell testers to close the app fully and open it again, twice if needed.
  3. Check the update is really running. Show the update label or update id on a debug screen of the staging build. Without that, a tester who never picked up the update will report that the fix works because the bug did not happen to them.
  4. Test the change and the paths around it. The screen you fixed, the start of the app, sign in, and anything that reads stored data.
  5. Promote the same release. Do not build the bundle again for Production. A new build of the bundle is a new, untested artifact.
# CodePush style CLI: release to Staging for one binary version
<cli> release-react MyApp-iOS ios -d Staging -t "2.4.0" --description "Fix cart total"

# After testing: copy the exact same release to Production
<cli> promote MyApp-iOS Staging Production

Replace <cli> with your service's command; the original CodePush CLI's promote "will create a new release for the destination deployment, which includes the exact code and metadata" of the source. With EAS, publish to the branch your preview builds read, then point production at the same update instead of publishing a fresh one:

# Expo EAS Update: publish to the branch the preview builds read
eas update --branch preview --message "Fix cart total"

Checklist before you promote

  • Target binary version or runtime version. It matches the binaries in your users' hands, and no older binary in that range is missing a native module the new JavaScript calls.
  • Mandatory flag. Decide on purpose. A mandatory update restarts or blocks the app for users, which is right for a crash fix and annoying for a copy change.
  • Rollout percentage. If your service supports it, start small. The original CodePush CLI takes a percentage from 1 to 100.
  • Rollback tested. Roll back once in Staging and confirm the app returns to the previous bundle cleanly. A rollback creates a new release with the code of the one before; make sure your team knows the command before it is needed at night.
  • Same React Native and Hermes setup. The bundle must be built with the same React Native version and Hermes setting as the binary. A bundle compiled for a different Hermes version will not run.
  • Data written by the old bundle. If the update changes how stored data is read, test on a device that ran the old bundle first, not on a fresh install.
  • Offline and slow networks. Start the app with no connection and on a slow one. The app should start on the bundle it has.
  • Update after a store release. When a new binary reaches the store, check that old updates do not target it by accident.

What an update cannot change

Anything in the native layer needs a new binary: a new native module or SDK, a new permission or usage description, a change to Info.plist or the Android manifest, a new minimum OS, the app icon, the React Native version. If the change touches any of these, it is a store release, and testers need a new build installed from a link, not an update.

What the store rules say

Read on November 3, 2026, on the official pages:

  • Apple, App Review Guideline 2.5.2: apps "may not download, install, or execute code which introduces or changes features or functionality of the app, including other apps." Guideline 4.7 separately allows software that is not embedded in the binary, specifically "HTML5 and JavaScript mini apps and mini games", with the developer responsible for it.
  • Google Play, Device and Network Abuse policy: "an app may not download executable code (such as dex, JAR, .so files) from a source other than Google Play." The policy adds: "This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser)."

In practice, teams use JavaScript updates for bug fixes and small changes, and ship new features through review. Reviewers judge how you use it, and neither store promises approval for any approach. Read the current text before you build a release process on it.

Where AppForShare fits

AppForShare shares the binaries; the update service ships the bundles. The two meet at the staging binary:

  • One link for the staging binary: a channel link and QR code on Pro and Team, so testers never hunt for the newest build when the native layer changes.
  • What changed between binaries: each upload is compared with the previous build of the same app (version, permissions, minimum OS, SDK, frameworks, size), which shows when a native change means a new binary is due.
  • Feedback tied to the update: the in-app feedback link takes an info parameter. Pass the update label there and every report says which bundle the tester was running, not just which binary.
  • A new binary prompt: on Pro and Team, the update check lets the staging build notice a newer binary in its channel and open the install page. It is for test builds only.

For the export and upload steps of a React Native release build, see sharing a React Native build with testers. Expo users can follow the EAS guide to upload a build straight from eas build.

Common questions

Can I test a CodePush update on a debug build?

You can test the JavaScript, but not the update. Debug builds load code from the packager. Test the update on a release build that reads the Staging deployment.

Do testers need a new build for every update?

No. They need a new build only when the native layer changes. Otherwise they restart the staging binary and it picks up the update.

Does AppForShare push code updates?

No. It shares whole test builds through install links. Use EAS Update, a CodePush compatible service or your own server for the updates.

Keep reading

App Distribution Platform · · 5 min read

AppForShare File Size and Upload Limits

AppForShare limits the size of each build you upload by plan: 50 MB without an account, 150 MB on the Free plan, 2 GB on Pro and 4 GB on Team. Accounts also have a storage total, and every plan...