What is CodePush?

Learn what CodePush is, how OTA updates reach React Native apps, what replaced Microsoft's service, and how to ship JS fixes without a store review.

CodePush is an over-the-air (OTA) update service for React Native apps. It lets you push JavaScript and asset changes directly to users' devices without submitting a new binary to the App Store or Google Play. Fixes reach users in minutes instead of days, and teams can deploy multiple times a day, just like web apps and backend services.

Looking for the CodePush product?

This guide covers CodePush as a technology: how OTA updates work for React Native and where they fit in a release process. If you're looking for Bitrise CodePush, our managed CodePush service, you can find it here.

What is CodePush?

CodePush is a service that delivers JavaScript bundle updates to React Native apps installed on mobile devices, skipping the app store release cycle for anything that doesn't touch native code. Microsoft built the original service as part of Visual Studio App Center, and it became the standard way React Native teams shipped hotfixes.

In practice, a React Native app is two things: a native binary (the shell users install from the store) and a JavaScript bundle (most of the code that runs inside it; the native side has critical code of its own, it just changes less often). When you fix a bug in JavaScript, the native binary doesn't change. CodePush takes advantage of that split. You release a new bundle to a CodePush server, and the app downloads it in the background and swaps it in.

If you've built for the web, the model will feel familiar. A browser downloads the latest app code the first time someone visits a page, and on later visits it either reuses the cached copy or fetches the newest version, depending on what the developer intended. CodePush brings that same update model to React Native apps.

CodePush is one implementation of a broader pattern called over-the-air updates, which covers any mechanism for updating app code or content without a store release. What made CodePush specifically popular was its workflow: named deployments (like Staging and Production), deployment keys baked into each app, staged rollouts, and one-command rollbacks.

While the original hosted service is gone, Microsoft retired Visual Studio App Center, including hosted CodePush, on March 31, 2025 (Microsoft's retirement notice), the CodePush protocol, SDK, and workflow live on through self-hosted servers and hosted alternatives, which we'll cover below.

How does CodePush work?

CodePush works through three parts: a CI workflow that builds your JavaScript bundle and releases it, an update server that stores the bundles and decides which one each device should get, and an SDK inside your app that checks for updates, downloads them, and applies them according to an install policy you choose.

The lifecycle looks like this:

  1. You build a new JavaScript bundle and release it to the CodePush server, targeting a deployment (Staging, Production).
  2. The app, on launch or resume, asks the server whether an update exists for its deployment key and binary version.
  3. If there's a match, the app downloads the new bundle in the background.
  4. The update installs based on your policy: on the next restart, on the next resume, or immediately for mandatory updates.
  5. If the new bundle crashes before it reports a successful start, the SDK rolls back to the previous working bundle automatically.

Deployment keys matter more than they first appear. Each build of your app carries a key that maps it to one deployment, so your internal testers on Staging get release candidates while Production users only see what you've promoted. Promotion moves a tested bundle between deployments without rebuilding anything.

In code, the standard integration is a codePush.sync() call when your root component mounts, and the SDK handles the rest:

import { useEffect } from "react";
import codePush from "react-native-code-push";

function App() {
  useEffect(() => {
    // Check for an update, download it silently,
    // and apply it on the next restart
    codePush.sync();
  }, []);

  return <YourAppContent />;
}

export default App;

Mandatory updates behave differently: when you mark a release as mandatory, the SDK applies it right away rather than waiting for a natural restart. That's useful for critical fixes, and easy to overuse (more on that in best practices).

Staged rollouts are built into the release flow. You can ship a bundle to a percentage of users, watch your crash and error metrics, then expand to everyone. And because OTA releases are still releases, most teams trigger them from their mobile CI/CD workflow rather than from a laptop, so every bundle is built, tested, and released the same way.

Why CodePush matters for mobile development

CodePush matters because the native release cycle is slow at exactly the moment you need speed: when a bug is live. A store release means building, submitting, waiting for review, then waiting again for users to update their apps. A CodePush release skips all of that for JavaScript changes and reaches devices in minutes.

Consider what a native hotfix actually costs. Apple reports that on average, 90% of App Store submissions are reviewed in less than 24 hours (Apple's App Review page), which is fast for a review process but it's still a day. Then users update on their own schedule, and even automatic updates trickle out over days as devices wait for Wi-Fi and idle time. A meaningful chunk of your audience can sit on a broken version for weeks.

Now compare the OTA path. You fix the JavaScript, release a bundle, and the app picks it up on next launch. No submission, no review queue, no waiting for users to visit the store. The window between "bug found" and "bug fixed for most users" drops from days or weeks to hours.

Traditional app store release flow with review delays compared with the CodePush flow delivering updates in minutes
Traditional app store release flow with review delays compared with the CodePush flow delivering updates in minutes.

That changes how teams behave, not only how fast they ship. When rollback is one command and a hotfix takes minutes, releasing stops feeling risky. Teams ship smaller changes more often because the cost of a mistake drops, which is why OTA updates belong in a deliberate mobile release management process rather than bolted on as an emergency tool.

CodePush does have a hard boundary: it only covers JavaScript and assets. Native module changes, new permissions, and SDK upgrades still need a store release. OTA updates complement the store cycle, they don't replace it. The stores draw their own lines too: our post on what app stores allow with OTA updates walks through Apple's and Google's policies in detail.

CodePush best practices

Treat OTA releases with the same discipline as store releases. That's the single most important practice. The distribution channel changed; the risk didn't. A bad bundle pushed to 100% of users breaks the app for 100% of users, faster than any store release ever could.

Here's what that discipline looks like in practice:

  1. Run OTA releases through your CI/CD workflow. Build the bundle in the same environment as your binaries, run your test suite against it, and release from a workflow, not a developer machine. This is standard continuous delivery applied to a new artifact type.
  2. Use staged rollouts and watch before expanding. Release to 5-10% of users, monitor crash rates and key metrics for a period you've agreed on in advance, then widen. The common mistake is rolling out to 100% because the fix "is obviously safe". The fixes that break things always looked obviously safe.
  3. Mark releases as mandatory sparingly. Mandatory installs interrupt users mid-session. Reserve them for genuine breakage: crashes on launch, security issues, broken payments. If every release is mandatory, you've traded user trust for convenience.
  4. Test the mandatory update flow on a real device from time to time. Mandatory updates only run during rare incidents, so the update experience can regress unnoticed between one emergency and the next. Push a mandatory release to your Staging deployment every few months and watch how the interruption looks and feels for a user.
  5. Test against the exact binary version you're targeting. A JavaScript bundle calls into native modules, and those modules differ between binary versions. A bundle that works against version 2.4 can crash against 2.3. Always verify the update on the actual binary versions that will receive it.
  6. Keep bundles small. Users download updates over whatever network they happen to be on. Audit your assets, avoid shipping unused images, and remember that a large bundle means slower delivery and more abandoned downloads.
  7. Have a rollback plan before you release. Know the command, know who's allowed to run it, and know what your automatic rollback behavior does when a bundle fails to start. The worst time to learn your rollback process is during an incident.

The most common mistake overall? Treating CodePush as a way to avoid process. It works best as a faster lane inside your existing release process, with the same gates, the same monitoring, and the same accountability.

Self-hosted CodePush vs managed CodePush

Since Microsoft retired the hosted service and archived its standalone server, teams keeping the CodePush workflow face one decision: run the open-source server yourself, or use a managed CodePush host. Self-hosting gives you full control and no usage fees. A managed service removes the operational work. The right answer depends on how much infrastructure your team wants to own.

Self-hosted CodePush Managed CodePush
Setup
Stand up the server, storage, and delivery infrastructure yourself
Create a deployment, add the SDK, get your keys
Maintenance
Yours. The archived Microsoft repo is read-only, so patches and fixes come from community forks you track yourself
The provider's. Server updates and fixes ship without your involvement
Reliability and scaling
Your infrastructure, your monitoring, your on-call
The provider's SLA and capacity
Update storage and delivery
You provision and pay for storage and bandwidth directly
Included and metered by the provider
Code signing
Available; you manage the setup and key handling
Built into the release flow
Cost structure
No fees, but real infrastructure and engineering time
Usage-based tiers, typically with a free tier

Self-hosting is a genuine option for teams with platform engineers to spare, but not necessarily for everyone else. An update server is production infrastructure serving your entire user base. If it goes down, updates stop; if it's compromised, someone else can push code to your app. That's a serious ownership decision, not a weekend setup.

Comparing managed options?

If you're weighing Bitrise CodePush against Expo's EAS Update, our in-depth comparison covers migration paths, pricing, and feature depth: Bitrise CodePush vs Expo EAS Update.

How Bitrise handles CodePush

Bitrise offers a hosted CodePush Server as part of Release Management, so teams that lost the hosted service when Microsoft retired App Center can keep their CodePush workflow without running their own infrastructure. The server is based on the Microsoft CodePush Server and integrates with Bitrise authentication, so access follows your existing Bitrise account and team setup.

It works with both React Native and Expo apps. On the client side, the Bitrise app SDK is the successor of the original CodePush SDK. It’s a drop-in replacement for the original SDK and Bitrise guarantees API compatibility with the original one. Its source is available at bitrise-io/react-native-code-push, and published on npm as @bitrise/code-push-sdk, and it supports the React Native New Architecture and Expo. 

Getting started with CodePush is a familiar picture:

  1. Create a CodePush deployment in Release Management, which gives you access to the server and your deployment keys.
  2. Add the SDK to your app and point it at your deployment.
  3. Push updates via the CodePush CLI, either manually or from a Bitrise workflow so your OTA releases go through the same build and test steps as everything else.

Update packages can be code-signed, so the app verifies that a bundle actually came from you before installing it. For anything shipping to production, turn this on.

Pricing is usage-based rather than seat-based. Usage is measured across three dimensions: monthly active users, data transfer, and storage. The Basic plan is free up to 100,000 MAU with 5,000 GiB of transfer and 5 GiB of storage, which covers a lot of production apps comfortably. Paid tiers scale from 250k up to 5M MAU for larger apps.

Since it lives inside Release Management, CodePush sits next to the rest of your release process: the workflows that build your binaries, the store submissions, and now the OTA lane for JavaScript fixes. One place to see what shipped, through which channel, to whom.

You can read the setup details in the CodePush documentation or get an overview on the Bitrise CodePush page.

See what Bitrise can do for you

Confidently build, test, and ship high-quality mobile apps with Bitrise.

Author

Oliver Falvai

Staff Engineer at Bitrise

Frequently Asked Questions

Is CodePush dead?

No. Microsoft's hosted CodePush service shut down when App Center was retired on March 31, 2025, but the protocol, SDK, and workflow continue. Teams now run the open-source server themselves or use a hosted provider like Bitrise. The day-to-day developer experience is the same.

What replaced Microsoft CodePush?

Microsoft published a standalone code-push-server for self-hosting; that repository was archived in May 2025 and is read-only, so ongoing development happens in community forks. Beyond self-hosting, hosted alternatives took over, including the Bitrise CodePush Server, which is based on Microsoft's server and works with the existing CodePush CLI workflow. Expo's EAS Update exists as a separate approach with its own protocol, not compatible with CodePush tooling.

Is CodePush allowed by Apple and Google?

Yes, within limits. Apple's App Review Guideline 2.5.2 says apps may not download, install, or execute code that introduces or changes the app's features or functionality. The carve-out that makes CodePush possible lives in the Apple Developer Program License Agreement, section 3.3.1(B): interpreted code such as JavaScript may be downloaded as long as it doesn't change the app's primary purpose, doesn't create a store for other code, and doesn't bypass OS security features. Google Play's Device and Network Abuse policy is similar in spirit: apps may not download executable code from outside Google Play, but code running in a virtual machine or interpreter, such as JavaScript, is exempt. Ship bug fixes and improvements, not a different app.

Does CodePush work with Expo?

Yes. The Bitrise fork of react-native-code-push supports Expo apps as well as bare React Native, including the New Architecture. Expo also ships its own OTA mechanism (expo-updates), so for Expo apps the choice comes down to which deployment workflow you want, not framework compatibility.

How big can a CodePush update be?

A CodePush update ships your JavaScript bundle plus any changed assets. On Bitrise CodePush, an update package can be up to 50 MB; if you self-host, the limit depends on your server setup. In practice, stay well under the cap: users download every update over whatever network they happen to be on, so audit assets before releasing and avoid bundling anything unused.