Guide · CodePush · OTA Updates

    CodePush for mobile apps: OTA updates without store review (2026)

    Nathan Claiborne
    CodePush lets you ship JavaScript and asset updates straight to installed mobile apps — no App Store or Play Store review, no waiting days for a hotfix to land. This guide explains how CodePush works, how to automate it from GitHub, and how to pair it with Corenna's signed native builds so you only go through store review when you actually change native code.

    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-push with 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-install or 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 sync
    import { 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:

    1. Native release — Corenna builds a signed .aab and .ipa from your GitHub repo and pushes them to TestFlight and Play Internal Testing.
    2. Promote to production — once the store version is live, it becomes the "shell" that hosts future OTA bundles.
    3. Iterate with OTA — every JS-only change ships via CodePush/Capgo within minutes, targeting the matching native binary version.
    4. 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 --rollout to 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.

    Keep reading