CodePush for mobile apps: OTA updates without store review (2026)
What CodePush is (and isn't)
- What it is: a service that hosts JS bundles and assets for React Native (and, via Capacitor Live Updates / Capgo, for Capacitor) and pushes them to installed apps at launch.
- What it isn't: a way to replace native code. Anything that changes Swift/Kotlin/Objective-C, native dependencies, or app permissions still needs a store submission.
- Why it matters: bug fixes, copy tweaks, UI iteration, and config flips ship in minutes, not days.
- Status: Microsoft retired the hosted App Center CodePush service in 2025. Active options today:
react-native-code-pushwith a self-hosted server, Expo Updates, Capgo, and Ionic Appflow Live Updates.
When to use OTA vs a fresh store build
- OTA-safe: JS logic, React components, styling, copy, images bundled with JS, feature flags, API endpoint changes.
- Requires a new store build: upgrading React Native or Capacitor, adding/removing native plugins, changing app icons, splash screens, permissions, bundle ID, or signing config.
- Rule of thumb: if
npx pod-installor a Gradle dependency change is involved, it's a native release — build it through Corenna and submit to the stores.
Setting up CodePush for React Native
The community-maintained client still works against a self-hosted or third-party CodePush-compatible server. Install and wire it up:
npm install react-native-code-push
cd ios && pod install && cd ..Wrap your root component:
import codePush from "react-native-code-push";
const App = () => <RootNavigator />;
export default codePush({
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_RESUME,
})(App);Add the deployment key to Info.plist (CodePushDeploymentKey) and strings.xml (CodePushDeploymentKey) for each platform. Use separate Staging and Production keys.
Setting up live updates for Capacitor
For Capacitor apps, Capgo and Ionic Appflow Live Updates fill the CodePush role. Capgo is the closest open-source equivalent:
npm install @capgo/capacitor-updater
npx cap syncimport { CapacitorUpdater } from "@capgo/capacitor-updater";
CapacitorUpdater.notifyAppReady();
// Capgo checks for and applies the next bundle on resume.Configure your channel (production/staging) in capacitor.config.ts and ship updates via the Capgo CLI or its GitHub Action.
Automating OTA releases from GitHub Actions
The whole point of OTA is short feedback loops. Wire it into CI so every merge to main ships a JS bundle automatically:
# .github/workflows/ota.yml
name: OTA release
on:
push:
branches: [main]
paths-ignore:
- "ios/**"
- "android/**"
- "package.json"
- "package-lock.json"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
# React Native (self-hosted CodePush server)
- run: npx code-push-cli release-react MyApp-iOS ios -d Production
env:
CODE_PUSH_ACCESS_KEY: ${{ secrets.CODE_PUSH_ACCESS_KEY }}
- run: npx code-push-cli release-react MyApp-Android android -d Production
env:
CODE_PUSH_ACCESS_KEY: ${{ secrets.CODE_PUSH_ACCESS_KEY }}
# Capacitor (Capgo)
# - run: npx @capgo/cli upload --channel production --apikey ${{ secrets.CAPGO_TOKEN }}The paths-ignore guard is the key bit — native changes skip OTA and go through the full Corenna signed build instead.
Pairing CodePush with Corenna signed builds
OTA and store builds are complementary, not competing. A healthy release pipeline looks like:
- Native release — Corenna builds a signed
.aaband.ipafrom your GitHub repo and pushes them to TestFlight and Play Internal Testing. - Promote to production — once the store version is live, it becomes the "shell" that hosts future OTA bundles.
- Iterate with OTA — every JS-only change ships via CodePush/Capgo within minutes, targeting the matching native binary version.
- Next native release — when you change plugins or native config, Corenna ships a new shell and the OTA cycle resets.
See the matching native build guides: AAB from GitHub, IPA from GitHub, and automated store delivery.
Staging, rollouts, and rollbacks
- Two deployments minimum — Staging (internal testers) and Production. Promote from Staging once green.
- Rollout percentage — release to 10% → 50% → 100% with
--rolloutto catch regressions on a fraction of users. - Mandatory updates — use sparingly. Reserve for security fixes; otherwise let users update lazily on app resume.
- Rollback — both CodePush and Capgo support instant rollback to the previous bundle if crash rates spike.
- Target binary version — pin OTA bundles to the native version they were built against to avoid shipping JS that calls native APIs the installed shell doesn't have.
Store policy: what's allowed
- Apple: JS/asset OTA updates are allowed under Guideline 3.3.2 as long as they don't change the app's primary purpose or violate other guidelines.
- Google Play: permitted as long as you don't ship executable code that bypasses Play's review (i.e. don't OTA arbitrary native binaries).
- Practical guidance: stay within JS, React components, styles, and bundled assets. Don't use OTA to add features that materially change what the app does versus the reviewed binary.
FAQ
Is App Center CodePush still available?
No — Microsoft retired the hosted App Center service in 2025. The react-native-code-push client is still maintained and works against self-hosted servers or compatible providers.
What's the best CodePush alternative in 2026?
For React Native: Expo Updates if you're on Expo, or a self-hosted CodePush server. For Capacitor: Capgo (open source) or Ionic Appflow Live Updates (managed).
Does Corenna host OTA updates?
Corenna focuses on signed native builds and store delivery. Pair Corenna with CodePush/Capgo/Expo Updates for OTA — the GitHub Action snippet above shows how the two pipelines coexist.
Can I OTA a native plugin update?
No. Anything that changes Podfile, Gradle dependencies, native source, permissions, or signing must go through a new store build.
How fast does an OTA update reach users?
By default, the client checks on app resume and installs on next resume. Users on the previous bundle typically receive the update within a day; mandatory updates apply on next launch.