SDK reference
SDK reference
- React Native
- Capacitor
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.
JavaScript API for @codemagic/capacitor-patch, the Capacitor client SDK. Install it with npm install @codemagic/capacitor-patch. It is a 0.x release, so its API may still change. The update functions (sync, checkForUpdate, downloadUpdate, installUpdate, notifyAppReady, isNextVersionReady, and others) match the React Native SDK. Use start() instead of Patch.wrap(). InstallMode and CheckFrequency are exported as in the React Native SDK.
For integration steps, see Native setup and Checking for updates. For step-by-step control without start() or sync(), see Manual control.
Requirements
- React Native
- Capacitor
- 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
- Capacitor
7.xor8.x(the two latest major versions) - Configuration in
capacitor.config.tsunderplugins.CodemagicPatch.{ios,android}, or native resource overrides - Android:
minSdkVersion24 or later, with CMake and the NDK available - iOS: Swift Package Manager or CocoaPods, with mixed Swift / Objective-C++ compilation
Native configuration
- React Native
- Capacitor
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.
| 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. Omit it to skip checks; that is not the same as treating releases as signed |
CodemagicPatchMaxLaunchAttempts | no | Positive integer. Consecutive launches a pending update may boot without notifyAppReady() before the SDK rolls it back. Default 3; see notifyAppReady() |
The SDK reads configuration from capacitor.config.ts (plugins.CodemagicPatch.ios and .android). Values in native resources (Info.plist / strings.xml) take precedence. There is no JavaScript configure() call.
Each platform block you add must include deploymentKey, apiUrl, and downloadBaseUrl. Run npx cap sync after changing the config. See Native setup.
If native code cannot determine the app binary version (CFBundleShortVersionString / versionName), the SDK no-ops and loads the embedded bundle.
Key in plugins.CodemagicPatch.{ios,android} | Required | Description |
|---|---|---|
deploymentKey | yes | Deployment key from cmpatch-capacitor deployment list |
apiUrl | yes | Patch API origin (server SERVER_URL), e.g. https://updates.example.com. The SDK calls /v1/... under this host |
downloadBaseUrl | yes | Artifact origin (server PUBLIC_BASE_URL), e.g. https://storage.example.com/codemagic-patch |
publicKey | no | PEM public key for release signature verification. If omitted, signatures are not checked |
maxLaunchAttempts | no | Positive integer. Number of consecutive launches a pending update can run without notifyAppReady() before it is rolled back. Default 3. Invalid values log a warning and fall back to the default. See notifyAppReady() |
Native resource keys (CodemagicPatchDeploymentKey, CodemagicPatchApiUrl, CodemagicPatchDownloadBaseUrl, CodemagicPatchPublicKey, CodemagicPatchMaxLaunchAttempts) override these values.
Functions
wrap(Root, options?)
- React Native
- Capacitor
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.
Not available in the Capacitor SDK. Use start(options?).
start(options?)
- React Native
- Capacitor
Capacitor only. In React Native, use wrap().
Runs sync(options) immediately and, with CheckFrequency.ON_APP_RESUME, each time the app returns to the foreground. Call it after the first screen renders:
import { CheckFrequency, InstallMode, start } from "@codemagic/capacitor-patch";
start({
checkFrequency: CheckFrequency.ON_APP_RESUME,
installMode: InstallMode.ON_NEXT_SUSPEND,
minimumBackgroundDuration: 60_000,
});
| Options | StartOptions: SyncOptions plus checkFrequency ("ON_APP_START" or "ON_APP_RESUME", default "ON_APP_START") |
| Returns | Promise<SyncStatus> for the first check |
| Throws | Never |
start()callssync(), which callsnotifyAppReady()first. A crash beforestart()counts toward rollback; a crash after it does not. If your app needs asynchronous initialization before it is usable, callstart()after that completes.- With
ON_APP_RESUME, a check runs on each transition frombackgroundorinactivetoactive. The plugin emits these transitions natively, so no other plugin is required.minimumBackgroundDurationaffects installation, not check frequency. - Each call starts a
sync(). Overlapping calls return"sync-in-progress". The latest call's options and check frequency replace earlier ones. start()has no progress callback or UI. For progress, callsync(options, onProgress)or use the manual APIs.
sync(options?, onProgress?)
End-to-end update flow: confirm running bundle → check → download → install.
- React Native
- Capacitor
import { InstallMode, sync } from "@codemagic/react-native-patch";
const status = await sync(
{
installMode: InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: InstallMode.IMMEDIATE,
},
({ receivedBytes, totalBytes }) => {
/* progress */
},
);
import { InstallMode, sync } from "@codemagic/capacitor-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.
- React Native
- Capacitor
import { checkForUpdate } from "@codemagic/react-native-patch";
const result = await checkForUpdate();
import { checkForUpdate } from "@codemagic/capacitor-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().
- React Native
- Capacitor
import { InstallMode, installUpdate } from "@codemagic/react-native-patch";
await installUpdate(local, { installMode: InstallMode.ON_NEXT_RESTART });
import { InstallMode, installUpdate } from "@codemagic/capacitor-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). Call it on startup if you do not use sync(), wrap() (React Native), or start() (Capacitor).
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 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
| 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.
- React Native
- Capacitor
import { getRunningBundleUpdateMetadata } from "@codemagic/react-native-patch";
const running = await getRunningBundleUpdateMetadata();
// { label: "v3", packageHash: "…", releaseNotes: "…" } for an OTA bundle, null for the embedded bundle
import { getRunningBundleUpdateMetadata } from "@codemagic/capacitor-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.
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.
- React Native
- Capacitor
import { isNextVersionReady, restartApp } from "@codemagic/react-native-patch";
if (await isNextVersionReady()) {
await restartApp();
}
import { isNextVersionReady, restartApp } from "@codemagic/capacitor-patch";
if (await isNextVersionReady()) {
await restartApp();
}
| Returns | Promise<boolean> |
| Throws | Does 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().
- React Native
- Capacitor
import { hydrate } from "@codemagic/react-native-patch";
await hydrate();
import { hydrate } from "@codemagic/capacitor-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
- React Native
- Capacitor
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.
Exported as a constant object and a TypeScript type of the same name. Used by start(): CheckFrequency.ON_APP_START (default) checks when start() is called; CheckFrequency.ON_APP_RESUME also checks on foreground return. String literals are also accepted.
InstallMode
- React Native
- Capacitor
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.
Exported as a constant object and a TypeScript type of the same name (import { InstallMode } from "@codemagic/capacitor-patch"). Use it for installMode or mandatoryInstallMode. String literals are also accepted. All four modes are supported; the plugin detects foreground and background transitions natively. Activating an update reloads the WebView.
| 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() (and by start() for its first check):
| 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; React Native 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() |
- React Native
- Capacitor
The React Native SDK throws only the codes above.
The Capacitor SDK can also throw:
| Code | Typical cause |
|---|---|
UNSUPPORTED_ON_WEB | Called on the web platform, for example under ionic serve in a browser |
CONFIGURATION_INVALID | A required plugins.CodemagicPatch key is missing or invalid for this platform |
INVALID_ARGUMENT | A native plugin method was called with a missing or empty argument. Only possible when calling the plugin directly, not through the public API |
- React Native
- Capacitor
import { CodemagicPatchError, CodemagicPatchErrorCode } from "@codemagic/react-native-patch";
try {
await checkForUpdate();
} catch (error) {
if (error instanceof CodemagicPatchError) {
console.log(error.code, error.message);
}
}
import { CodemagicPatchError, CodemagicPatchErrorCode } from "@codemagic/capacitor-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
- React Native
- Capacitor
Wire this in AppDelegate / MainApplication, Native setup.
The Capacitor plugin selects the package in its load() method and points the WebView at it. No app code is needed. See Native setup.