Manual control (advanced)
Manual control (advanced)
If you need to separate the steps, e.g. download silently but let the user decide when to restart, or gate updates behind a "What's new" prompt, use the lower-level functions instead of sync():
- React Native
- Capacitor
import {
InstallMode,
checkForUpdate,
downloadUpdate,
installUpdate,
notifyAppReady,
restartApp,
disallowRestart,
allowRestart,
} from "@codemagic/react-native-patch";
// 1) Confirm the running bundle is healthy (arms rollback). Call this once on
// startup if you are NOT using sync(), e.g. after your app finishes booting.
await notifyAppReady();
// 2) Check, then download with progress.
const check = await checkForUpdate();
if (check.action === "ota-update") {
const local = await downloadUpdate(check.remotePackage, (p) =>
console.log(p.receivedBytes, "/", p.totalBytes),
);
// 3) Install. With IMMEDIATE the bundle reloads now; with ON_NEXT_RESTART it
// waits for the next launch.
await installUpdate(local, { installMode: InstallMode.ON_NEXT_RESTART });
// 4) Optionally force a reload yourself (e.g. after the user taps "Update now").
await restartApp(/* onlyIfUpdateIsPending */ true);
}
// Suppress restarts during a critical flow (checkout, video call, …), then re-enable.
disallowRestart();
// … later …
allowRestart();
import {
InstallMode,
checkForUpdate,
downloadUpdate,
installUpdate,
notifyAppReady,
restartApp,
disallowRestart,
allowRestart,
} from "@codemagic/capacitor-patch";
// 1) Confirm the running bundle is healthy (arms rollback). Call this once on
// startup if you use neither start() nor sync(), e.g. after the first screen renders.
await notifyAppReady();
// 2) Check, then download with progress.
const check = await checkForUpdate();
if (check.action === "ota-update") {
const local = await downloadUpdate(check.remotePackage, (p) =>
console.log(p.receivedBytes, "/", p.totalBytes),
);
// 3) Install. With IMMEDIATE the WebView reloads now; with ON_NEXT_RESTART it
// waits for the next launch.
await installUpdate(local, { installMode: InstallMode.ON_NEXT_RESTART });
// 4) Optionally force a reload yourself (e.g. after the user taps "Update now").
await restartApp(/* onlyIfUpdateIsPending */ true);
}
// Suppress restarts during a critical flow (checkout, video call, …), then re-enable.
disallowRestart();
// … later …
allowRestart();
If you do not call sync() on every successful startup, call notifyAppReady() yourself once the app has booted successfully. A sync() call behind a user action does not replace startup readiness confirmation. Repeated launches without confirmation eventually exhaust the pending bundle's launch-attempt budget and trigger rollback.
Stage-specific UI
For download progress and the final result alone, call sync(options, onProgress) and await its status. For checking, downloading, and installing indicators, own the steps and update your app's state around each call:
- React Native
- Capacitor
import {
checkForUpdate,
downloadUpdate,
installUpdate,
InstallMode,
type DownloadProgress,
} from "@codemagic/react-native-patch";
type UpdateStage =
| "checking" | "downloading" | "installing"
| "up-to-date" | "skipped-failed" | "ready-on-restart" | "error";
// Call from your chosen lifecycle or button handler, without Patch.wrap().
// Confirm startup readiness separately as described above.
async function checkAndStageUpdate(
setStage: (stage: UpdateStage) => void,
onProgress: (progress: DownloadProgress) => void,
) {
try {
setStage("checking");
const check = await checkForUpdate();
if (check.action === "up-to-date") {
setStage("up-to-date");
return;
}
if (check.action === "ota-update") {
if (check.remotePackage.previouslyFailed) {
setStage("skipped-failed");
return;
}
// If your app needs user confirmation, request it here before download.
setStage("downloading");
const local = await downloadUpdate(check.remotePackage, onProgress);
setStage("installing");
await installUpdate(local, { installMode: InstallMode.ON_NEXT_RESTART });
} else {
// Server-requested embedded revert has no package to download.
setStage("installing");
await installUpdate(check, { installMode: InstallMode.ON_NEXT_RESTART });
}
setStage("ready-on-restart");
} catch (error) {
setStage("error");
console.error("Update failed", error); // Replace with your error reporting.
}
}
import {
InstallMode,
checkForUpdate,
downloadUpdate,
installUpdate,
type DownloadProgress,
} from "@codemagic/capacitor-patch";
type UpdateStage =
| "checking" | "downloading" | "installing"
| "up-to-date" | "skipped-failed" | "ready-on-restart" | "error";
// Call from your chosen lifecycle or button handler.
// Confirm startup readiness separately as described above.
async function checkAndStageUpdate(
setStage: (stage: UpdateStage) => void,
onProgress: (progress: DownloadProgress) => void,
) {
try {
setStage("checking");
const check = await checkForUpdate();
if (check.action === "up-to-date") {
setStage("up-to-date");
return;
}
if (check.action === "ota-update") {
if (check.remotePackage.previouslyFailed) {
setStage("skipped-failed");
return;
}
// If your app needs user confirmation, request it here before download.
setStage("downloading");
const local = await downloadUpdate(check.remotePackage, onProgress);
setStage("installing");
await installUpdate(local, { installMode: InstallMode.ON_NEXT_RESTART });
} else {
// Server-requested embedded revert has no package to download.
setStage("installing");
await installUpdate(check, { installMode: InstallMode.ON_NEXT_RESTART });
}
setStage("ready-on-restart");
} catch (error) {
setStage("error");
console.error("Update failed", error); // Replace with your error reporting.
}
}
These stages are app-owned UI state, not SDK callbacks. Disable repeated checks while this operation runs. This example deliberately stages all updates, including mandatory releases, for the next restart. To preserve sync()'s default immediate installation of mandatory updates, select the install mode from check.remotePackage.isMandatory; see Applying updates. An immediate install may reload the app before a completion indicator is visible.
API summary
- React Native
- Capacitor
| Function | Purpose |
|---|---|
sync(options?, onProgress?) | End-to-end: confirm → check → download → install. Returns a SyncStatus; never throws. |
checkForUpdate() | Returns { action: "up-to-date" | "ota-update" | "embedded-revert", remotePackage? }. |
downloadUpdate(remotePackage, onProgress?) | Downloads (patch or full bundle) and returns a LocalPackage. |
installUpdate(target, options?) | Stages/applies a downloaded package using an installMode. |
notifyAppReady() | Confirms the running bundle as good (rollback protection). |
getRunningBundleUpdateMetadata() | Returns { label, packageHash, releaseNotes } for the running OTA bundle, or null for the embedded bundle. |
isNextVersionReady() | Whether a different package is already installed and waiting for the next reload. Does not report a staged return to the embedded bundle. |
restartApp(onlyIfUpdateIsPending?) | Reloads the JS bundle to apply a pending update. |
disallowRestart() / allowRestart() | Block / unblock SDK-triggered restarts during critical flows. |
| Function | Purpose |
|---|---|
sync(options?, onProgress?) | End-to-end: confirm → check → download → install. Returns a SyncStatus; never throws. |
checkForUpdate() | Returns { action: "up-to-date" | "ota-update" | "embedded-revert", remotePackage? }. |
downloadUpdate(remotePackage, onProgress?) | Downloads (patch or full bundle) and returns a LocalPackage. |
installUpdate(target, options?) | Stages/applies a downloaded package using an installMode. |
notifyAppReady() | Confirms the running bundle as good (rollback protection). |
getRunningBundleUpdateMetadata() | Returns { label, packageHash, releaseNotes } for the running OTA bundle, or null for the embedded bundle. |
isNextVersionReady() | Whether a different package is already installed and waiting for the next reload. Does not report a staged return to the embedded bundle. |
restartApp(onlyIfUpdateIsPending?) | Reloads the WebView to apply a pending update. |
disallowRestart() / allowRestart() | Block / unblock SDK-triggered restarts during critical flows. |
Most apps use start(options?), which calls sync(). If you call the individual steps instead, call notifyAppReady() on every successful startup.
For when to check, see Checking for updates. For install modes and production UX patterns, see Applying updates.
UX hook points in manual flow
Manual control is where most teams wire "Check for updates" and "Apply update" buttons.
Typical mapping:
checkForUpdate()powers "Check now"downloadUpdate()progress powers download indicatorsinstallUpdate(..., { installMode })decides when a downloaded update becomes activeisNextVersionReady()powers "Update ready" while the current bundle keeps runningrestartApp(true)powers explicit "Restart now" actionsdisallowRestart()/allowRestart()protect critical screens while a pending update exists
Use these hooks to connect Patch behavior to your app's existing prompt, splash, and navigation patterns.