Skip to main content

Migrating from CodePush

Migrating from CodePush

This guide covers moving from any CodePush-compatible client and server (community forks, hosted successors, or a self-hosted CodePush-compatible API) to self-hosted Codemagic Patch. The wire protocol and package names differ by fork; the apps / deployments / sync() mental model usually transfers. For trade-offs, see How Patch compares.

The steps below replace a React Native CodePush-compatible SDK with @codemagic/react-native-patch.

warning

The Patch SDK ships in the native binary. Plan a store release (or internal distribution build) that embeds Patch before OTA updates can run through your Patch server. You cannot swap the update client purely over the air.

Concept mapping​

CodePush fork (typical)Codemagic Patch
Hosted or self-hosted CodePush-compatible serverSelf-hosted Patch stack on your domains
App access key / service tokenOAuth sign-in (CLI/dashboard) + cm_pat_… API tokens (CI/automation)
Deployment key in native configCodemagicPatchDeploymentKey (same idea)
Staging / Production deploymentsSame channel names by default
codePush.sync() / HOC wrappersync() / Patch.wrap(App, options?) from @codemagic/react-native-patch
release-react (CodePush CLI)cmpatch release-react
--mandatory, rollout, target binary versionSame flags on cmpatch release-react / release patch
notifyAppReady()Called automatically by sync(); manual if not using sync()

Install modes (ON_NEXT_RESTART, IMMEDIATE, etc.) and mandatoryInstallMode map closely, see Applying updates.

1. Stand up Patch​

Follow Install (or the Local quickstart to try the stack locally first). You need:

  • API URL → CodemagicPatchApiUrl
  • Download base URL → CodemagicPatchDownloadBaseUrl

Create apps to mirror your CodePush layout (one app per platform is the usual pattern). From your project root, cmpatch init connects to the server, signs you in, creates one app per platform, and links the project:

cmpatch init
cmpatch deployment list --app MyApp-iOS --format table

Copy each deployment key into config when you integrate the SDK:

Info.plist / strings.xml, or the Expo config plugin. See Native setup.

2. Replace the client SDK​

Remove your CodePush client package and native wiring (package name varies by fork):

yarn remove @code-push-next/react-native-code-push # or your fork's package

While CodePush is still referenced from the native hosts, the wiring cmpatch init runs reports it as a conflict and leaves the native and JavaScript steps for later; once it is gone, run cmpatch wire to install the SDK, run pod install, and apply the native changes described in Native setup.

Replace CodePush calls in JS (example uses @code-push-next; adjust the import for your fork):

// Before (CodePush)
import codePush from "@code-push-next/react-native-code-push";
codePush.sync({ installMode: codePush.InstallMode.ON_NEXT_RESTART });

// After (Patch)
import { InstallMode, sync } from "@codemagic/react-native-patch";
void sync({ installMode: InstallMode.ON_NEXT_RESTART });

If you used the codePush(...)(App) HOC, replace it with Patch.wrap(App, options):

- import codePush from "@code-push-next/react-native-code-push";
+ import * as Patch from "@codemagic/react-native-patch";

- export default codePush({ installMode: codePush.InstallMode.ON_NEXT_RESUME })(App);
+ export default Patch.wrap(App, { installMode: Patch.InstallMode.ON_NEXT_RESUME });

The wrapper is for basic automatic updates: it renders your root immediately and calls sync() after mount. checkFrequency accepts "ON_APP_START" (default) or "ON_APP_RESUME" (mount plus foreground return). Use Patch.CheckFrequency and Patch.InstallMode constants, or their string values; do not reuse CodePush's numeric constants or copy the old options object wholesale.

For progress UI, user confirmation, or readiness that depends on asynchronous initialization, use direct APIs instead of the wrapper. See Manual control. The wrapper reference covers its props, refs, and lifecycle.

Convert background duration to milliseconds​

CodePush's minimumBackgroundDuration is in seconds; Patch's is in milliseconds. Convert the value when migrating ON_NEXT_RESUME or ON_NEXT_SUSPEND:

- minimumBackgroundDuration: 60, // CodePush: one minute
+ minimumBackgroundDuration: 60_000, // Patch: one minute

Keeping 60 would reduce the threshold to 60 milliseconds. This option controls installation timing, not how often you check for updates; see Applying updates.

Replace a MANUAL wrapper without losing readiness confirmation​

The legacy codePush({ checkFrequency: codePush.CheckFrequency.MANUAL })(App) wrapper still called notifyAppReady() on mount. Patch has no MANUAL wrapper policy. Remove the wrapper and preserve that startup responsibility separately from your user-triggered checks:

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

export default App; // no update wrapper

// Call from your successful startup path, after required initialization.
async function onAppStarted() {
await Patch.notifyAppReady();
}

// Connect separately to your "Check for updates" button.
async function onCheckForUpdates() {
const status = await Patch.sync();
return status; // Use the result in your UI.
}

Do not wait for the user to press the button to confirm readiness: an unconfirmed healthy update can eventually roll back after repeated launches without confirmation. If your startup path already calls sync() after initialization succeeds, it confirms readiness itself and no separate notifyAppReady() call is needed.

Move custom behavior to direct APIs​

The legacy HOC discovered methods on the wrapped class component; these were not callback fields in its options. Choose the manual integration that matches what those methods did:

Legacy behaviorPatch migration
codePushDownloadDidProgressUse sync(options, onProgress) for download progress; see progress and status.
codePushStatusDidChange for completion or errorsAwait sync() and handle its returned status. Patch returns status strings, not CodePush's numeric SyncStatus values.
codePushStatusDidChange for checking / downloading / installing UIUpdate your UI around each direct API call; see the manual example. sync() does not expose intermediate status callbacks.
updateDialogShow your own confirmation UI before download or installation using manual control.
codePushOnBinaryVersionMismatchUse checkForUpdate() and its store-version fields; see store updates. sync() returns only a status.
Custom static properties on the wrapped componentwrap() does not hoist them. Keep the original component where consumers need its statics, or explicitly expose the properties your integration requires.
Per-call deploymentKey overrideNot supported by Patch's JS APIs. Configure deployment keys in the native build; see per-build configuration. Apps that switch deployments at runtime need to revise that integration.
ignoreFailedUpdates / rollbackRetryOptionsNo equivalent sync() options. Patch's sync() skips packages marked previouslyFailed; it does not provide CodePush's configurable rollback retry policy.

Use one owner for automatic checks. When replacing the wrapper with manual lifecycle code, remove Patch.wrap() rather than adding a second sync() call to observe its work: overlapping calls return "sync-in-progress" and do not attach callbacks to the running operation.

3. Point CI at Patch​

Install and authenticate the Patch CLI:

npm install -g @codemagic/patch-cli
cmpatch token create --name ci # store cm_pat_… as a CI secret

Commit the codemagic-patch.config.json that cmpatch init wrote, and CI needs only the token; without it, set CODEMAGIC_PATCH_SERVER_URL.

Swap your fork's release / release-react CI steps for:

cmpatch release-react \
--platform ios \
--deployment Production \
--release-notes "…" \
--yes

See CI integration.

4. Cut over​

  1. Publish a native build with the Patch SDK and deployment keys embedded (Staging first).
  2. Publish an OTA release to that deployment with cmpatch release-react targeting the same binary version users install.
  3. Validate on Staging, then repeat for Production.
  4. Decommission the old CodePush server or SaaS once no active binaries still call it.

Checklist​

  • Patch server running with HTTPS and an OAuth sign-in provider configured
  • Apps and deployments created; keys copied into native config
  • CodePush package and native wiring removed; Patch SDK integrated
  • Patch.wrap(), sync(), or manual APIs replace CodePush sync/HOC
  • Background duration converted to milliseconds; Patch constants replace CodePush constants
  • Startup readiness preserved when removing a MANUAL HOC
  • Legacy callbacks, custom statics, and unsupported options reviewed against the migration table
  • CI secrets and release commands updated to cmpatch
  • New store/internal binary shipped; OTA tested on Staging before Production