npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@codemagic/react-native-patch

v0.5.0

Published

React Native client SDK for Codemagic Patch — over-the-air JavaScript & asset updates for React Native and Expo apps.

Readme

@codemagic/react-native-patch

Client SDK for Codemagic Patch: a self-hosted CodePush successor for over-the-air updates on React Native and Expo apps. Download, verify, and boot JS/asset bundles on device so you can ship JS changes without a new store binary.

This package is only the on-device side. You also need a Patch server (self-hosted) and the cmpatch CLI to publish releases. The monorepo has both, plus a dashboard and a local evaluation stack.

What you get

From the monorepo:

  • Full stack via Docker Compose (API, worker, Postgres, MinIO, dashboard)
  • Web dashboard with RBAC and release management
  • On-device demo app for trying OTA updates quickly

From this SDK:

  • Binary patches when available, for smaller downloads (falls back to a full bundle)

  • Signature verification when you configure a public key

  • Automatic rollback if a new package fails before the app reports ready

  • Expo config plugin for prebuild projects, plus bare React Native wiring

  • One-line Patch.wrap(App) root wrapper, CodePush-style sync(), or step-by-step APIs when you need control

Requirements

  • React Native 0.73+ — New Architecture support starts at RN 0.76; RN 0.73–0.75 are supported on the Old (Paper) Architecture only
  • Android minSdkVersion 23+ (the library follows the host's rootProject.ext.minSdkVersion; without one it builds at 23)
  • Android native build with CMake/JNI support
  • iOS native build with CocoaPods and mixed Swift/ObjC++ compilation
  • Expo SDK 52+ (via the bundled config plugin — see Expo apps)
  • Expo Go is not supported — the native module is not part of the Expo Go runtime

Installation

npm install @codemagic/react-native-patch
# or
yarn add @codemagic/react-native-patch

react (>=18) and react-native (>=0.73) are peer dependencies. On iOS, install the native pod:

cd ios && pod install

Quick start

Configuration lives in native resources (deployment key, API URL, download base URL, optional public key), not in a JS config object. The Expo plugin writes those for you; bare apps set them in Info.plist / strings.xml and wire bundle selection.

Expo (config plugin)

If you maintain native projects manually, follow manual native setup.

{
  "plugins": [
    ["@codemagic/react-native-patch", {
      "ios": {
        "deploymentKey": "<key>",
        "downloadBaseUrl": "<url>",
        "apiUrl": "<url>",
        "publicKey": "<pem>"
      },
      "android": {
        "deploymentKey": "<key>",
        "downloadBaseUrl": "<url>",
        "apiUrl": "<url>",
        "publicKey": "<pem>"
      }
    }]
  ]
}

Configure at least one platform. Every block you provide needs deploymentKey, downloadBaseUrl, and apiUrl (publicKey and maxLaunchAttempts are optional). Then run expo prebuild. See Configuration for where each value comes from.

Bare React Native

Add CodemagicPatchDeploymentKey, CodemagicPatchApiUrl, and CodemagicPatchDownloadBaseUrl (optional CodemagicPatchPublicKey) to Android strings.xml and iOS Info.plist, then point RN’s JS bundle path at CodemagicPatch.getJSBundleFile / CodemagicPatch.bundleURL() so boot order is pending package → current package → embedded bundle. Full snippets are in Configuration below.

Wrap the root component

import * as Patch from "@codemagic/react-native-patch";

function App() {
  return <AppNavigator />;
}

export default Patch.wrap(App);

wrap() renders your app right away and runs sync() once the root has mounted. With no options, ordinary updates install on the next restart and mandatory updates install immediately; pass WrapOptions as the second argument to change that (Patch.wrap(App, { installMode: Patch.InstallMode.ON_NEXT_RESUME })).

Set checkFrequency: "ON_APP_RESUME" to check on mount and on foreground return; the default "ON_APP_START" checks only on mount. For example: Patch.wrap(App, { checkFrequency: Patch.CheckFrequency.ON_APP_RESUME, installMode: Patch.InstallMode.ON_NEXT_RESTART }). For manual checks, use the direct APIs without wrap(). Patch.CheckFrequency and Patch.InstallMode provide named constants; existing string literals remain supported.

wrap() forwards props and refs to your root component. It does not copy custom static properties, such as App.navigationOptions, onto the wrapper.

Mounting the wrapped root is what reports the app as ready: sync() calls notifyAppReady() first, which confirms the running package and arms rollback for the next one. If your app has to finish an asynchronous bootstrap (authentication, database migration) before it can vouch for the running bundle, skip wrap() and call sync() yourself at that point:

import { sync } from "@codemagic/react-native-patch";
import { bootstrapApp } from "./bootstrap"; // Your app's initialization routine.

await bootstrapApp(); // If initialization fails, do not confirm the bundle.
const status = await sync();
// "update-installed" | "up-to-date" | "embedded-revert-applied"
// | "sync-in-progress" | "error"

sync() never throws. It checks for an update, downloads and installs when appropriate, and reports app readiness so a bad package can roll back.

For check / download / install as separate steps, restart timing helpers (restartApp, allowRestart / disallowRestart), getRunningBundleUpdateMetadata() to read which OTA release is running, or isNextVersionReady() to learn whether an installed update is waiting for the next reload, see the SDK types.

Configuration

Codemagic Patch is configured through native resources, not a JS API — the deployment key, URLs, public key, and binary version are read natively before the SDK initializes. Provide these values per host app:

| Resource | Required | Meaning | | --- | --- | --- | | CodemagicPatchDeploymentKey | yes | Deployment key for this app/track | | CodemagicPatchApiUrl | yes | API server origin (the server's SERVER_URL), e.g. https://updates.example.com. The SDK appends /v1/... | | CodemagicPatchDownloadBaseUrl | yes | Artifact origin (the server's PUBLIC_BASE_URL), e.g. https://storage.example.com/codemagic-patch. May include a bucket/path prefix; the SDK appends manifest/artifact paths | | CodemagicPatchPublicKey | no | PEM public key; required only when enforcing client-side signature verification | | CodemagicPatchMaxLaunchAttempts | no | Positive integer; consecutive launches a pending update may boot without notifyAppReady() before crash rollback. Default 3; see notifyAppReady() |

The two URLs point at different systems (API server vs. object storage / CDN), which is why one usually carries a path and the other does not.

Where the values come from: the deployment key is the DEPLOYMENT_KEY column of cmpatch deployment list --app <app-name> --format table (or the deployment's page in the dashboard); the API URL and download base URL are printed by the self-host installer when the server is set up. Use separate apps — and therefore separate deployment keys — for iOS and Android.

Bare React Native

  1. Declare the resources.

    iOS — ios/<YourApp>/Info.plist:

    <key>CodemagicPatchDeploymentKey</key>
    <string>ios-staging-deployment-key</string>
    <key>CodemagicPatchDownloadBaseUrl</key>
    <string>https://storage-updates.example.com/codemagic-patch</string>
    <key>CodemagicPatchApiUrl</key>
    <string>https://updates.example.com</string>
    <!-- optional, only when enforcing code signing -->
    <key>CodemagicPatchPublicKey</key>
    <string>-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----</string>
    <!-- optional, defaults to 3 -->
    <key>CodemagicPatchMaxLaunchAttempts</key>
    <integer>3</integer>

    Android — android/app/src/main/res/values/strings.xml:

    <resources>
      <string name="CodemagicPatchDeploymentKey" translatable="false">android-staging-deployment-key</string>
      <string name="CodemagicPatchDownloadBaseUrl" translatable="false">https://storage-updates.example.com/codemagic-patch</string>
      <string name="CodemagicPatchApiUrl" translatable="false">https://updates.example.com</string>
      <!-- optional, only when enforcing code signing -->
      <string name="CodemagicPatchPublicKey" translatable="false">-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----</string>
      <!-- optional, defaults to 3 -->
      <string name="CodemagicPatchMaxLaunchAttempts" translatable="false">3</string>
    </resources>
  2. Wire native bundle selection before the RN bridge starts, so boot order is pending package → current package → embedded bundle.

    Android — feed CodemagicPatch.getJSBundleFile(applicationContext) into React Native in MainApplication. On RN ≤ 0.81 (ReactNativeHost), override getJSBundleFile():

    override fun getJSBundleFile(): String? =
        CodemagicPatch.getJSBundleFile(applicationContext)

    On RN 0.82+ (new-arch reactHost), pass it as jsBundleFilePath:

    override val reactHost: ReactHost by lazy {
      getDefaultReactHost(
        context = applicationContext,
        packageList = PackageList(this).packages,
        jsBundleFilePath = CodemagicPatch.getJSBundleFile(applicationContext),
      )
    }

    iOS — import CodemagicPatchClient and override bundleURL() / sourceURL(for:) in AppDelegate with the same selection order. Keep the DEBUG branch pointing at Metro so local development keeps working:

    import CodemagicPatchClient
    
    class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
      override func sourceURL(for bridge: RCTBridge) -> URL? {
        self.bundleURL()
      }
    
      override func bundleURL() -> URL? {
    #if DEBUG
        RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
    #else
        CodemagicPatch.bundleURL() ?? Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
      }
    }

    On RN ≤ 0.76, where the app template still ships an Objective-C++ AppDelegate.mm, override sourceURLForBridge: with the same selection. The native module is a Swift pod exposed to Objective-C as @objc(CodemagicPatch); its generated -Swift.h is not on the host target's header search paths and @import is unavailable in ObjC++, so forward-declare the surface you call:

    // CodemagicPatch is a Swift pod exposed as @objc(CodemagicPatch); forward-declare it —
    // the implementation links in from the pod's static library.
    @interface CodemagicPatch : NSObject
    + (NSURL *_Nullable)bundleURL;
    @end
    
    - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
    {
    #if DEBUG
      return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
    #else
      return [CodemagicPatch bundleURL] ?: [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
    #endif
    }

    Expected embedded bundle names are index.android.bundle (Android) and main.jsbundle (iOS). If the SDK cannot determine a non-blank binary version (versionName / CFBundleShortVersionString), it no-ops and falls back to the embedded bundle.

    Debug builds load JS from Metro, so OTA updates are not picked up there — that is expected, not a wiring problem. To see an update apply, run a release-style build.

  3. Register the native module for your architecture — the TurboModule (New Architecture) or the bridge module/package (Old Architecture). Autolinking handles this in most apps.

Expo apps

For projects using Prebuild, use the bundled config plugin (app.plugin.js) as shown in Quick start. Native projects maintained manually use the native configuration and bundle-selection steps instead. The plugin configuration is:

{
  "plugins": [
    ["@codemagic/react-native-patch", {
      "ios":     { "deploymentKey": "<key>", "downloadBaseUrl": "<url>", "apiUrl": "<url>", "publicKey": "<pem>" },
      "android": { "deploymentKey": "<key>", "downloadBaseUrl": "<url>", "apiUrl": "<url>", "publicKey": "<pem>" }
    }]
  ]
}

The plugin writes the native resources and wires bundle selection automatically (Configuration steps 1–3). Configure at least one platform, and complete every block you provide — deploymentKey, downloadBaseUrl, and apiUrl are all required per block (publicKey and maxLaunchAttempts are optional). The plugin resolves expo/config-plugins from your app's own Expo SDK, so there is nothing extra to install; expo is not a runtime or peer dependency of this package.

Usage

The simplest integration is Patch.wrap(App), which renders your root component immediately and runs sync() after it mounts. sync() checks for an update, downloads it, installs it, and reports app readiness in one call. It never throws — it resolves to a status string. Call it directly when you need to pick the moment yourself, for example after an asynchronous bootstrap:

import { sync } from "@codemagic/react-native-patch";
import { bootstrapApp } from "./bootstrap"; // Your app's initialization routine.

await bootstrapApp(); // If initialization fails, do not confirm the bundle.
const status = await sync();
// "update-installed" | "up-to-date" | "embedded-revert-applied"
// | "sync-in-progress" | "error"

For finer control, drive the steps yourself and call notifyAppReady() once the app has started successfully (so the update is not rolled back):

import {
  checkForUpdate,
  downloadUpdate,
  installUpdate,
  notifyAppReady,
} from "@codemagic/react-native-patch";

restartApp, allowRestart / disallowRestart, and hydrate are also exported for controlling reload timing, getRunningBundleUpdateMetadata() reports the running OTA release ({ label, packageHash, releaseNotes }, or null on the embedded bundle), and isNextVersionReady() reports whether a newer package is installed and waiting for the next reload. See the type definitions for the full API surface.

Documentation

Full integration guide, update protocol, and self-hosting instructions live in the Codemagic Patch repository.

Coming from react-native-code-push? The CodePush migration guide maps the native config resources, JS APIs, and CLI commands to their Codemagic Patch equivalents.

License

Apache-2.0