Skip to main content

Checking for updates

Checking for updates

sync() is the one call most apps need on the client. It checks the server, downloads an update if one matches this deployment key and binary version, installs it, and confirms the running bundle is healthy, all in one invocation.

This page covers when and how to check and download. For when a downloaded update becomes active, see Applying updates.

What sync() does​

On each call, in order:

  1. notifyAppReady(): marks the currently running bundle as healthy. If a pending bundle repeatedly starts without reaching notifyAppReady(), the SDK rolls it back once its launch-attempt budget is exhausted. Because sync() calls this first, running it on every startup confirms the previous update and arms rollback for the next one.
  2. Check the server for a matching update.
  3. Download the new bundle (or a binary patch, with automatic fallback to the full bundle).
  4. Install according to the install mode you choose (see Applying updates).

sync() never throws; it resolves to a SyncStatus: "up-to-date", "update-installed", "embedded-revert-applied", "sync-in-progress", or "error". In React Native development builds an "error" result is also logged as a warning with its reason (an unreachable server, a failed download or install). A wrong deployment key or path is not an error: the server answers 404, which the SDK treats as "no update", so silence there does not confirm the configuration. With no options, non-mandatory releases wait for the next cold start (installMode); mandatory releases apply immediately (mandatoryInstallMode).

Minimal integration​

Wrap your root component with Patch.wrap(App) (cmpatch wire adds this for you). Your app renders immediately, and the wrapper calls sync() after the root mounts:

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

function App() {
return <YourApp />;
}

export default Patch.wrap(App);

Pass WrapOptions as the second argument to customize install timing, for example Patch.wrap(App, { installMode: Patch.InstallMode.ON_NEXT_RESUME }). With no options, ordinary updates install on the next restart and mandatory updates install immediately. Set checkFrequency: "ON_APP_RESUME" to also check on foreground return. The default "ON_APP_START" checks only on mount. See the wrapper reference for API details.

If your app must finish asynchronous initialization before confirming readiness, call sync() after it succeeds instead of using wrap(); see the readiness guidance.

For step-by-step control (checkForUpdate, downloadUpdate, installUpdate), see Manual control. For rollouts and mandatory flags on the server, see Production control.

When to check for updates​

sync() is safe to call more than once. Concurrent calls resolve to "sync-in-progress".

TriggerTypical use
App launchCatch updates published while the app was closed
Return to foregroundCatch updates published while the user had the app open
User action"Check for updates" in settings, after login, etc.

To check on launch and foreground return:

export default Patch.wrap(App, {
checkFrequency: Patch.CheckFrequency.ON_APP_RESUME,
installMode: Patch.InstallMode.ON_NEXT_RESTART,
});

For manual timing or asynchronous readiness, skip the wrapper and call sync() from your own lifecycle code.

Combine with install modes from Applying updates so resume-based installs do not surprise users mid-flow.

Download progress and status​

For progress UI, remove Patch.wrap() and call sync() from your chosen lifecycle or button handler with the optional progress callback. Keep startup readiness confirmation as described in Manual control. A second sync() cannot observe the wrapper's in-flight operation: it returns "sync-in-progress" without attaching its callback.

Pass your install options and progress callback to the direct call:

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

const result = await sync(
{
installMode: InstallMode.ON_NEXT_RESTART,
mandatoryInstallMode: InstallMode.IMMEDIATE,
},
({ receivedBytes, totalBytes }) => {
const pct = totalBytes > 0 ? receivedBytes / totalBytes : 0;
// drive UI
},
);

With manual control, the same { receivedBytes, totalBytes } shape is passed to downloadUpdate().

Use sync() result statuses ("update-installed", "up-to-date", "error") to decide whether to show messaging after a check completes.

Release notes from the server​

Each OTA release can carry release notes (CLI --release-notes, dashboard, or CI). On the client, RemotePackage.releaseNotes is populated when checkForUpdate() returns { action: "ota-update" }.

Patch does not ship a built-in update dialog. Show notes in your own UI before calling installUpdate, or after sync() returns "update-installed" for deferred install modes. Changing notes on the server does not require a new native binary; republish or patch the release metadata.

Binary version mismatch and store updates​

The server targets updates with targetBinaryVersion. When the installed native binary is too old for the latest OTA on a deployment, checkForUpdate() (and therefore sync()) may return { action: "up-to-date" } even though newer store builds exist.

Every check result includes:

FieldMeaning
isStoreUpdateAvailableNative binary on device is below the server's latest known store version
latestBinaryVersionLatest binary version the server knows for this app (from release metadata)

Use these to prompt a store update instead of silently doing nothing:

const check = await checkForUpdate();

if (check.isStoreUpdateAvailable && check.latestBinaryVersion) {
// Show "Update from the App Store / Play Store", your UX
}

if (check.action === "ota-update") {
// proceed with download / install
}

Server-side targeting is covered in Production control.

Next steps​

  • Applying updates: install modes, staging vs production behavior, mandatory apply timing
  • Manual control: button-driven check, download, and apply flows