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.
| Key | Required | Description |
|---|---|---|
CodemagicPatchDeploymentKey | yes | Deployment key from cmpatch deployment list |
CodemagicPatchApiUrl | yes | Patch API origin (server SERVER_URL), e.g. https://updates.example.com. The SDK calls /v1/... under this host |
CodemagicPatchDownloadBaseUrl | yes | Artifact origin (server PUBLIC_BASE_URL), e.g. https://storage.example.com/codemagic-patch |
CodemagicPatchPublicKey | no | PEM public key when the app enforces release signature verification |
CodemagicPatchMaxLaunchAttempts | no | Positive integer. Consecutive launches a pending update may boot without notifyAppReady() before the SDK rolls it back. Default 3; see notifyAppReady() |
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.
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, extendingSyncOptionswithcheckFrequency: "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 asApp.navigationOptionsare not copied. - Lifecycle: each mount starts a
sync(). Overlapping calls, including React Strict Mode's repeated effect or nested wrappers, use thesync()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 onbackground/inactivetoactivetransitions. Repeatedactiveevents are ignored; the listener is removed on unmount.minimumBackgroundDurationcontrols installation only, not check frequency. - Scope: no loading screen, update dialog, or progress/status callbacks. There is no
MANUALfrequency; use directsync(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.
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 */
},
);
| Returns | Promise<SyncStatus>, see Sync status |
| Throws | Never, failures resolve to "error" |
| Concurrency | Second 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();
| Returns | Promise<UpdateCheckResult> |
| Throws | CodemagicPatchError 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);
| Returns | Promise<LocalPackage> |
| Throws | CodemagicPatchError, 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 });
| Arguments | InstallTarget, LocalPackage from downloadUpdate(), or embedded-revert result from checkForUpdate() |
| Returns | Promise<void> |
| Throws | CodemagicPatchError, 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). Required on startup when not using sync().
await notifyAppReady();
| Returns | Promise<void> |
| Throws | Does 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) and ship a new binary; an OTA update cannot change it.
restartApp(onlyIfUpdateIsPending?)
Reload the JS bundle to apply a pending update.
await restartApp(true); // only reload if an update is waiting
| Default | onlyIfUpdateIsPending = false |
| Returns | Promise<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
| Returns | Promise<RunningBundleUpdateMetadata | null>, see RunningBundleUpdateMetadata |
| Throws | Does 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.
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
| Field | Type | Default | Description |
|---|---|---|---|
installMode | InstallMode | ON_NEXT_RESTART | When non-mandatory releases apply |
mandatoryInstallMode | InstallMode | IMMEDIATE | When mandatory releases apply |
minimumBackgroundDuration | number | 0 | Min 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.
| Value | Behavior |
|---|---|
ON_NEXT_RESTART | Apply on next cold start |
ON_NEXT_RESUME | Apply when returning to foreground (after minimumBackgroundDuration) |
ON_NEXT_SUSPEND | Apply when entering background (after minimumBackgroundDuration) |
IMMEDIATE | Reload as soon as install completes |
Sync status
Values returned by sync():
| Status | Meaning |
|---|---|
"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; development builds log the reason with console.warn) |
UpdateCheckResult
Discriminated union on action:
action | Meaning |
|---|---|
"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:
| Field | Type | Description |
|---|---|---|
isStoreUpdateAvailable | boolean | Device binary is below server's latest known store version |
latestBinaryVersion | string | null | Latest binary version from server metadata |
RemotePackage
Package offered by the server (from checkForUpdate().remotePackage):
| Field | Type | Description |
|---|---|---|
packageHash | string | Content hash |
label | string | Release label (e.g. v3) |
deploymentKey | string | Deployment this release belongs to |
releaseNotes | string | null | Server-provided notes |
isMandatory | boolean | Mandatory flag from server |
fullBundleUrl | string | null | Full bundle download URL |
patchUrl | string | null | Binary patch URL |
fullBundleSize | number | Full bundle size in bytes |
patchSize | number | null | Patch size in bytes |
previouslyFailed | boolean | This package failed on device before |
LocalPackage
Extends RemotePackage with:
| Field | Type | Description |
|---|---|---|
installedAt | string | ISO 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):
| Field | Type | Description |
|---|---|---|
label | string | Release label (e.g. v3) captured when the package was installed |
packageHash | string | Content hash of the running package |
releaseNotes | string | null | Release 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.
| Code | Typical cause |
|---|---|
NETWORK_ERROR | Manifest fetch or download failed |
INVALID_MANIFEST | Malformed server manifest |
SIGNATURE_MISMATCH | Release signature verification failed |
INTEGRITY_ERROR | Hash or patch apply failed |
DOWNLOAD_IN_PROGRESS | Concurrent downloadUpdate() |
SYNC_IN_PROGRESS | Reserved for internal use |
NOT_DOWNLOADED | installUpdate() without prior download |
INVALID_UPDATE_TARGET | Wrong package passed to installUpdate() |
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:
IMMEDIATEinstalls queue activation instead of reloadingrestartApp()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:
- Pending OTA package (downloaded, not yet active)
- Current confirmed OTA package
- Embedded bundle shipped with the app
Wire this in AppDelegate / MainApplication, Native setup.