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, first sync() call), 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.

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

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

sync(options?, onProgress?)

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

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

const status = await sync(
{ installMode: "ON_NEXT_RESTART", mandatoryInstallMode: "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().

await installUpdate(local, { 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). Required on startup when not using sync().

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.


restartApp(onlyIfUpdateIsPending?)

Reload the JS bundle 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.


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

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

InstallMode

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():

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 (see internal state / logs; sync() does not throw)

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()
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.