Skip to main content

SDK reference

SDK reference

JavaScript API for @codemagic/react-native-patch: the React Native client SDK.

For integration steps (native wiring, Expo plugin, root wrapper), see Native setup and Checking for updates. For step-by-step control without sync(), see Manual control.

Requirements​

  • React Native >=0.73, React >=18. New Architecture support starts at RN 0.76; RN 0.73–0.75 are supported on the Old (Paper) Architecture only
  • Expo SDK 52+ with a development build (Expo Go is not supported)
  • Native config keys set at build time: see below

Native configuration​

The SDK reads configuration from native resources, not from a JS configure() call.

Bare RN: set in Info.plist / strings.xml and wire bundle selection in AppDelegate / MainApplication. Expo: use the config plugin, Native setup.

If native code cannot determine the app binary version (CFBundleShortVersionString / versionName), the SDK no-ops and loads the embedded bundle.

KeyRequiredDescription
CodemagicPatchDeploymentKeyyesDeployment key from cmpatch deployment list
CodemagicPatchApiUrlyesPatch API origin (server SERVER_URL), e.g. https://updates.example.com. The SDK calls /v1/... under this host
CodemagicPatchDownloadBaseUrlyesArtifact origin (server PUBLIC_BASE_URL), e.g. https://storage.example.com/codemagic-patch
CodemagicPatchPublicKeynoPEM public key when the app enforces release signature verification. Omit it to skip checks; that is not the same as treating releases as signed
CodemagicPatchMaxLaunchAttemptsnoPositive integer. Consecutive launches a pending update may boot without notifyAppReady() before the SDK rolls it back. Default 3; see notifyAppReady()

Functions​

wrap(Root, options?)​

Returns a React component that renders Root immediately and calls sync(options) from its mount effect. Define the wrapped component at module scope, outside render:

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

export default Patch.wrap(App, {
installMode: Patch.InstallMode.ON_NEXT_RESUME,
});
  • Options: WrapOptions, extending SyncOptions with checkFrequency: "ON_APP_START" | "ON_APP_RESUME" (optional, defaults to "ON_APP_START"). Defaults: ordinary updates → ON_NEXT_RESTART, mandatory updates → IMMEDIATE.
  • Returns: WrappedRootComponent, with the root's props and ref types preserved. Props and refs are forwarded; the root must itself support the ref you pass. Custom static properties such as App.navigationOptions are not copied.
  • Lifecycle: each mount starts a sync(). Overlapping calls, including React Strict Mode's repeated effect or nested wrappers, use the sync() concurrency guard. A later remount can start a new check; this is not a once-per-process guarantee.
  • Foreground checks: "ON_APP_RESUME" checks on mount and on background/inactive to active transitions. Repeated active events are ignored; the listener is removed on unmount. minimumBackgroundDuration controls installation only, not check frequency.
  • Scope: no loading screen, update dialog, or progress/status callbacks. There is no MANUAL frequency; use direct sync(options, onProgress) without the wrapper for manual checks, progress, and the final status.

Because sync() calls notifyAppReady() first, root mount confirms the running bundle as healthy. If your app must complete asynchronous initialization before confirming readiness, call sync() or notifyAppReady() yourself after it succeeds instead of using wrap(). See Checking for updates.


start(options?)​

Capacitor only. In React Native, use wrap().


sync(options?, onProgress?)​

End-to-end update flow: confirm running bundle → check → download → install.

import { InstallMode, sync } from "@codemagic/react-native-patch";

const status = await sync(
{
installMode: InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: InstallMode.IMMEDIATE,
},
({ receivedBytes, totalBytes }) => {
/* progress */
},
);
ReturnsPromise<SyncStatus>, see Sync status
ThrowsNever, failures resolve to "error"
ConcurrencySecond call while one is running returns "sync-in-progress"

Calls notifyAppReady() first on every invocation. Uses mandatoryInstallMode when the remote release is mandatory; otherwise installMode. Defaults: non-mandatory → ON_NEXT_RESTART, mandatory → IMMEDIATE.

If the server offers an update that previously failed on this device, sync() returns "up-to-date" without retrying.


checkForUpdate()​

Check the server without downloading.

import { checkForUpdate } from "@codemagic/react-native-patch";

const result = await checkForUpdate();
ReturnsPromise<UpdateCheckResult>
ThrowsCodemagicPatchError on network/manifest failures

Every result includes isStoreUpdateAvailable and latestBinaryVersion for store-update prompts when OTA is not offered.


downloadUpdate(remotePackage, onProgress?)​

Download the bundle (patch preferred, full bundle fallback) after a successful check.

const local = await downloadUpdate(result.remotePackage, onProgress);
ReturnsPromise<LocalPackage>
ThrowsCodemagicPatchError, e.g. DOWNLOAD_IN_PROGRESS, INTEGRITY_ERROR, SIGNATURE_MISMATCH

remotePackage must match the package from the latest checkForUpdate() that returned { action: "ota-update" }.


installUpdate(target, options?)​

Stage or apply a downloaded package, or handle an embedded revert from checkForUpdate().

import { InstallMode, installUpdate } from "@codemagic/react-native-patch";

await installUpdate(local, { installMode: InstallMode.ON_NEXT_RESTART });
ArgumentsInstallTarget, LocalPackage from downloadUpdate(), or embedded-revert result from checkForUpdate()
ReturnsPromise<void>
ThrowsCodemagicPatchError, e.g. NOT_DOWNLOADED, INVALID_UPDATE_TARGET

InstallOptions: installMode, minimumBackgroundDuration (ms, for ON_NEXT_RESUME / ON_NEXT_SUSPEND).


notifyAppReady()​

Mark the currently running bundle as healthy (rollback protection). Call it on startup if you do not use sync(), wrap() (React Native), or start() (Capacitor).

await notifyAppReady();
ReturnsPromise<void>
ThrowsDoes not throw

If a pending update is running for the first time, this promotes it to the confirmed good bundle. sync() calls this automatically at the start of each run.

A pending update that boots without reaching notifyAppReady() counts one unconfirmed launch. After CodemagicPatchMaxLaunchAttempts consecutive unconfirmed launches (default 3) the SDK rolls back to the previous bundle and reports Failed. The count is unconfirmed launches, not crashes: the OS ending a healthy process before JS runs also spends one, and a confirmed launch resets it. A lower value catches a broken bundle sooner but rolls back more healthy updates after such kills. Change it in native config (Info.plist / strings.xml, or maxLaunchAttempts in the Expo plugin or capacitor.config.ts) and ship a new binary; an OTA update cannot change it.


restartApp(onlyIfUpdateIsPending?)​

Reload the JS bundle (React Native) or the WebView (Capacitor) to apply a pending update.

await restartApp(true); // only reload if an update is waiting
DefaultonlyIfUpdateIsPending = false
ReturnsPromise<void>

No-op when onlyIfUpdateIsPending is true and there is no pending package. Respects restart suppression.


disallowRestart() / allowRestart()​

Block or unblock SDK-triggered reloads (including after IMMEDIATE installs).

disallowRestart();
// … critical UX …
allowRestart();

allowRestart() may flush a reload that was queued while blocked.


getRunningBundleUpdateMetadata()​

Identify which bundle is running: the OTA release label, package hash, and release notes, or null for the embedded binary bundle.

import { getRunningBundleUpdateMetadata } from "@codemagic/react-native-patch";

const running = await getRunningBundleUpdateMetadata();
// { label: "v3", packageHash: "…", releaseNotes: "…" } for an OTA bundle, null for the embedded bundle
ReturnsPromise<RunningBundleUpdateMetadata | null>, see RunningBundleUpdateMetadata
ThrowsDoes not throw

The result describes the bundle loaded for this process and does not change until the next reload — installing an update or calling notifyAppReady() leaves it as is. Use packageHash to compare bundles; label is display-oriented and unique only within a deployment.


isNextVersionReady()​

Whether a different package is already installed and takes over on the next reload (restartApp(), an install-mode lifecycle trigger, or a cold start). Use it to show an "Update ready" prompt while the current bundle keeps running.

getRunningBundleUpdateMetadata() reports what is running now, and returns null on the embedded bundle. That is exactly when a first OTA is waiting, so this is a separate call rather than a field on that result.

import { isNextVersionReady, restartApp } from "@codemagic/react-native-patch";

if (await isNextVersionReady()) {
await restartApp();
}
ReturnsPromise<boolean>
ThrowsDoes not throw

false at boot; true once sync() or installUpdate() lands a newer package in the same process. A staged return to the embedded bundle is not reported: reverting clears the pending slot instead of filling it, so this stays false even though the next reload switches back to the embedded bundle.


hydrate()​

Ensure the SDK has loaded on-disk state before other calls. Called automatically by all public async APIs; export alias for ensureHydrated().

import { hydrate } from "@codemagic/react-native-patch";
await hydrate();

Types and constants​

SyncOptions​

FieldTypeDefaultDescription
installModeInstallModeON_NEXT_RESTARTWhen non-mandatory releases apply
mandatoryInstallModeInstallModeIMMEDIATEWhen mandatory releases apply
minimumBackgroundDurationnumber0Min background time (ms) before ON_NEXT_RESUME / ON_NEXT_SUSPEND activate

CheckFrequency​

Exported as a constant object and a same-name TypeScript type. Use Patch.CheckFrequency.ON_APP_START (default, mount only) or Patch.CheckFrequency.ON_APP_RESUME (mount plus foreground return). Existing string literals remain supported. There is no MANUAL value; use the direct APIs without wrap() for manual timing.

InstallMode​

Exported as a constant object and a same-name TypeScript type. Use Patch.InstallMode.ON_NEXT_RESTART, for example, for either installMode or mandatoryInstallMode. Named imports (import { InstallMode } from "@codemagic/react-native-patch") and existing string literals are also supported.

ValueBehavior
ON_NEXT_RESTARTApply on next cold start
ON_NEXT_RESUMEApply when returning to foreground (after minimumBackgroundDuration)
ON_NEXT_SUSPENDApply when entering background (after minimumBackgroundDuration)
IMMEDIATEReload as soon as install completes

Sync status​

Values returned by sync() (and by start() for its first check):

StatusMeaning
"up-to-date"No applicable update, or skipped previously failed package
"update-installed"Downloaded and installed (may still be pending activation per install mode)
"embedded-revert-applied"Reverted to the embedded bundle per server manifest
"sync-in-progress"Another sync() is already running
"error"Check, download, or install failed (sync() does not throw; React Native development builds log the reason with console.warn)

UpdateCheckResult​

Discriminated union on action:

actionMeaning
"up-to-date"No OTA to install
"ota-update"remotePackage populated, call downloadUpdate()
"embedded-revert"Server instructs revert to embedded bundle, pass result to installUpdate()

All variants include:

FieldTypeDescription
isStoreUpdateAvailablebooleanDevice binary is below server's latest known store version
latestBinaryVersionstring | nullLatest binary version from server metadata

RemotePackage​

Package offered by the server (from checkForUpdate().remotePackage):

FieldTypeDescription
packageHashstringContent hash
labelstringRelease label (e.g. v3)
deploymentKeystringDeployment this release belongs to
releaseNotesstring | nullServer-provided notes
isMandatorybooleanMandatory flag from server
fullBundleUrlstring | nullFull bundle download URL
patchUrlstring | nullBinary patch URL
fullBundleSizenumberFull bundle size in bytes
patchSizenumber | nullPatch size in bytes
previouslyFailedbooleanThis package failed on device before

LocalPackage​

Extends RemotePackage with:

FieldTypeDescription
installedAtstringISO timestamp when downloaded
source"patch" | "full_bundle"Which artifact type was used

RunningBundleUpdateMetadata​

Running OTA package identity (from getRunningBundleUpdateMetadata(); null when the embedded bundle is running):

FieldTypeDescription
labelstringRelease label (e.g. v3) captured when the package was installed
packageHashstringContent hash of the running package
releaseNotesstring | nullRelease notes captured when the package was installed; null when the release was published without notes

DownloadProgress​

{ receivedBytes: number; totalBytes: number }

Errors​

Low-level APIs throw CodemagicPatchError with a code field. sync() catches errors and returns "error" instead.

CodeTypical cause
NETWORK_ERRORManifest fetch or download failed
INVALID_MANIFESTMalformed server manifest
SIGNATURE_MISMATCHRelease signature verification failed
INTEGRITY_ERRORHash or patch apply failed
DOWNLOAD_IN_PROGRESSConcurrent downloadUpdate()
SYNC_IN_PROGRESSReserved for internal use
NOT_DOWNLOADEDinstallUpdate() without prior download
INVALID_UPDATE_TARGETWrong package passed to installUpdate()

The React Native SDK throws only the codes above.

import { CodemagicPatchError, CodemagicPatchErrorCode } from "@codemagic/react-native-patch";

try {
await checkForUpdate();
} catch (error) {
if (error instanceof CodemagicPatchError) {
console.log(error.code, error.message);
}
}

Restart suppression​

When disallowRestart() is active:

  • IMMEDIATE installs queue activation instead of reloading
  • restartApp() reloads only if no pending update is waiting (or suppression is lifted)

Use during checkout, onboarding, or other flows where an unexpected reload would be disruptive. See Applying updates.


Boot order (native)​

On cold start, native bundle selection prefers, in order:

  1. Pending OTA package (downloaded, not yet active)
  2. Current confirmed OTA package
  3. Embedded bundle shipped with the app

Wire this in AppDelegate / MainApplication, Native setup.