Migrate from Capgo
Replace @capgo/capacitor-updater with the HotCodePush Capacitor SDK at your next store release, with every channel, config key, call and event mapped.
A Capacitor app leaves Capgo by replacing @capgo/capacitor-updater with @hotcodepush/capacitor-live-updates at its next store release. The store build that carries the new SDK is the switch, and the builds before it keep taking Capgo updates until their users move. What differs, side by side, is on the site's Capgo alternative page.
HotCodePush serves no Capgo-compatible endpoint, so the plugin's updateUrl cannot be pointed at it. Three things Capgo has are not here: end-to-end encryption of bundles, a default channel set on the server, and self-hosting. Bundles are signed and verified, not encrypted, the channel is in the build, and there is no self-hosted edition.
AI-assisted migration (recommended)
Install the HotCodePush skills:
npx skills add hotcodepush-team/skillsThen paste the prompt into your coding agent:
Migrate this app from Capgo to HotCodePush: install the skills with `npx skills add hotcodepush-team/skills`, then run the hotcodepush-migrate skill, following https://hotcodepush.com/docs/migrate/capgo.The agent removes the Capgo plugin, runs init, maps channels, config keys, SDK calls and binary ranges, and reports a checklist for the store release. It never touches Capgo, and the first release waits for the store build. Agent skills explains what the agent does.
Prerequisites
- You have a HotCodePush account and an organization.
npx hotcodepush initcreates both, as the quickstart shows. - The app is a Capacitor app on a supported major. The minimum versions are on Supported versions, and Capgo serves older ones that HotCodePush does not.
- A store release lies ahead: the swap ships in a native build, so plan an App Store and a Play Store submission.
- Node.js is on every machine that makes a native build, since the build step runs the CLI from an Xcode phase and a Gradle task.
Steps
If you already removed the plugin and ran init, skip to step 4.
1. Freeze a branch for the old builds
git branch capgo-fleetA bundle built from the migrated code imports a native module the old binaries lack. Hotfixes for them come from a branch that keeps the old client.
2. Remove the Capgo plugin
npm uninstall @capgo/capacitor-updaterDelete the CapacitorUpdater block from plugins in capacitor.config.ts. Delete the CapacitorUpdater.notifyAppReady() call and its import from the entry file, or from plugins/capacitorUpdater.client.ts in a Nuxt app. Replace every call and listener per the SDK calls below, then run npx cap sync.
Keep .capgo_key_v2 where the Capgo hotfix job reads it: the branch of step 1 still uploads with it.
Give the splash screen its auto-hide back
Capgo's onboarding set launchAutoHide: false on @capacitor/splash-screen and autoSplashscreen: true on the plugin when instant updates were on. The SDK has no splash-screen key: the native layer picks the bundle before the WebView loads, and an immediate reload waits for the first render. Restore launchAutoHide, or call SplashScreen.hide() yourself.
3. Run init
npx hotcodepush initinit signs you in, picks or creates the organization and the app with its production channel, installs @hotcodepush/capacitor-live-updates and writes hotcodepush.json. It wires the build step, an Xcode phase and one apply from line in android/app/build.gradle. The Capacitor SDK's Installation page shows the manual steps for a project it could not edit.
It asks whether to generate a signing key, which Sign your bundles explains, while Capgo's encryption key stays with Capgo. Decline the first release for now.
Release after the store build, not before
A release created before a binary was built is never served to that binary. The release that reaches your new store builds is the one you create in step 6, after step 5 built them.
Then import the package in the app's entry file, where notifyAppReady() was, even when no call follows:
import '@hotcodepush/capacitor-live-updates';The import reports the first render, which is what notifyAppReady() did. An app that imports the package late, or not at all, confirms no update, and every one is rolled back after readyTimeoutSeconds.
4. Create the channels your Capgo channels had
production exists. Each further Capgo channel becomes one here, with a name of letters, digits, - and _:
npx hotcodepush channel create --name beta --discoverable--discoverable is Capgo's "Allow device self-assignment": setChannel({ name }) resolves the name on the device, as Channels describes. Without it the channel is reachable by its id alone. A build that set defaultChannel sets channel in hotcodepush.json or HOTCODEPUSH_CHANNEL in its build environment instead, as Set up environments and tenants shows.
5. Ship the store release that carries the SDK
Build and submit the release as before, an Xcode archive and a Gradle release variant. The build step runs binary create inside the build and registers it with its fingerprint, so the machine needs npx hotcodepush login or HOTCODEPUSH_TOKEN. Under CI a store build without a token fails. npx hotcodepush binary list shows the registered builds, and there is no native version to compare against afterwards.
6. Release from your pipeline
npm run build && npx hotcodepush release create --bundle-version "$(git rev-parse --short HEAD)" --yesThis line replaces npx @capgo/cli bundle upload --channel production --apikey "$CAPGO_TOKEN". With HOTCODEPUSH_TOKEN and, for a signing app, HOTCODEPUSH_SIGNING_KEY in the environment, it is the whole pipeline step. The command reads webDir from capacitor.config. Ship every commit is the pipeline it sits in.
Open the app on a device that runs the new store build: the release applies within about a minute (How an update reaches a device). If nothing happens, see Troubleshooting.
7. Run both until the fleet moves
A binary carries one plugin: a Capgo bundle never reaches a HotCodePush build, and the reverse. Keep the Capgo plan and pipeline while old builds have users. Capgo's MAU shows how many still check in. Capgo has no free plan after its trial, so stop once the old builds are below the share you leave behind, then cancel Capgo.
Never upload a web build without notifyAppReady() to Capgo
An old build rolls a bundle back after appReadyTimeout, 10 seconds, when notifyAppReady() never runs, and marks it failed. The branch of step 1 keeps the call.
CLI commands
| Capgo | HotCodePush |
|---|---|
npx @capgo/cli init <apikey> | npx hotcodepush init |
login, --apikey, CAPGO_TOKEN | login, HOTCODEPUSH_TOKEN |
app add, app list, app delete | app create, app list, app delete |
app debug | device probe, and showDebugScreen() on the device |
app setting | Edit hotcodepush.json and rebuild. |
bundle upload --channel production | release create --channel production |
bundle upload without --channel | bundle upload |
bundle upload -c production,beta | release create --channel production --channel beta |
--bundle 1.2.3, --auto-bump | --bundle-version 1.2.3. There is nothing to bump, since the label is free text. |
--comment "Fixes the cart" | --notes "Fixes the cart". The notes ride the public index, and the commit is recorded on its own. |
--rollout 5 | --rollout-percent 5 |
--key-v2 .capgo_key_v2, --key-data-v2 | --private-key-path hotcodepush-private-key.pem, or the key's text in HOTCODEPUSH_SIGNING_KEY |
--delta, --tus | Nothing to pass. Deltas are always on. |
--min-update-version 2.0.0, --auto-min-update-version | --binary-version ">=2.0.0" ranges over the store version, and the fingerprint rides every release on its own. |
bundle compatibility, --fail-on-incompatible | The fingerprint is checked on the device. hotcodepush fingerprint diff names what moved, and the upload warns when no binary matches. |
bundle list, bundle delete, bundle cleanup | bundle list and bundle delete stay, while cleanup is retention. |
channel add beta --self-assign | channel create --name beta --discoverable |
channel add --default | None. The build's channel picks it. |
channel set --bundle 1.2.3 | release create --bundle 17, or release rollback --to-release 12 |
channel set --disable-auto-update major | Nothing. The fingerprint decides, or --binary-version for a range of store versions. |
channel set --rollout-pause, --rollout-resume | release pause, release resume |
channel set --rollout-promote | release rollout --rollout-percent 100 |
channel set --rollout-rollback | release revoke --release 43 |
the auto-pause flags of channel set | --failure-threshold-percent, --failure-minimum-update-count and --failure-action on channel create, channel update and release create |
channel delete --success-if-not-found | channel delete --ignore-not-found |
channel currentBundle | release list --channel beta |
| Unlink, on the channel page | channel pause |
| Built-in Bundle, on the channel page | release revoke --all |
key create, key save | signing-key create writes publicKeys itself. |
get-qr | A preview channel has the QR code. |
Capgo's bundle versions were unique semver. Here a bundle is numbered per app, and its version label is free text (Bundles and releases). Capgo calls a store binary a release, while here a release is a bundle put on a channel, numbered per channel. The CLI reference lists every command.
What maps to what
plugins.CapacitorUpdater | hotcodepush.json | Note |
|---|---|---|
appId | appId | This is the HotCodePush app id, not the bundle id. init writes it. |
defaultChannel | channel, or HOTCODEPUSH_CHANNEL per build | The build step resolves it to the channel's id. |
autoUpdate: 'atBackground', the default | applyStrategy: next_resume with nextResumeMinimumBackgroundSeconds: 0 | This is the closest moment, while the default next_start waits for a cold start. |
autoUpdate: 'always', directUpdate: 'always' | applyStrategy: immediate | The reload waits for the app's first render. |
autoUpdate: 'atInstall', 'onLaunch' | no exact match | immediate applies at any check. |
autoUpdate: 'onlyDownload' | applyStrategy: manual | applyUpdate() finishes the update. |
autoUpdate: 'off' | checkStrategy: manual | sync() starts each cycle. |
periodCheckDelay | checkIntervalSeconds, 900 by default, at least 60 | Capgo checks on every foreground. Here a resume checks when the last check is older than the interval. |
appReadyTimeout: 10000 | readyTimeoutSeconds: 10 | It is in seconds, with no off switch. |
autoDeleteFailed, autoDeletePrevious, resetWhenUpdate | none | A failed bundle is blocked on the device, and the SDK keeps the current, the next and the fallback release. |
publicKey | publicKeys | These are signature keys, from signing-key create. |
version | none | The build step reads the store build's version. |
autoSplashscreen, keepUrlPathAfterReload | none | |
updateUrl, statsUrl, channelUrl, allowModifyUrl, the local* keys | none | |
shakeMenu, allowShakeChannelSelector | showDebugScreen() behind a gesture of your own | It shows state only, with no channel picker. |
the nativeUpdatePrompt* keys | none |
The Capacitor SDK's Configuration page lists every key with its type and default.
SDK calls
| Capgo | HotCodePush | Note |
|---|---|---|
notifyAppReady() | Nothing. The import in the entry file is the signal. | notifyReady() under readySignal: manual replaces it. |
getLatest() | checkForUpdate() | It answers available or up_to_date. Nothing is downloaded. |
download(), then next() | downloadUpdate({ applyStrategy: 'manual' }) | Without the option the update applies per applyStrategy. |
set(), reload() | applyUpdate() | It reloads only when an update waits. |
triggerUpdateCheck() | sync() | It resolves with the outcome. |
setMultiDelay(), cancelDelay() | setRestartAllowed({ isAllowed: false }), then { isAllowed: true }, and nextResumeMinimumBackgroundSeconds | There is no date or nativeVersion delay. |
reset() | clearUpdates() | It reloads onto the embedded bundle. |
reset({ toLastSuccessful: true }), setBundleError() | rollbackUpdate({ reason }) | |
current(), getNextBundle(), getFailedUpdate() | getState() | currentRelease, nextRelease, failedBundleIds |
list(), delete() | none | The SDK keeps the current, the next and the fallback release. |
setChannel({ channel, triggerAutoUpdate }) | setChannel({ name }), then sync() | The choice is persisted. |
unsetChannel() | setChannel(null) | |
getChannel() | getChannel() | id, name and source |
listChannels() | none | |
setCustomId() | setAttributes({ userId }) | Attributes are labels, never personal data. |
getDeviceId(), getBuiltinVersion() | getDevice() | id, binaryVersion |
| the URL setters, the preview and app-store methods | none | |
the download event, percent from 0 to 100 | downloadProgress, progress from 0 to 1 | |
updateAvailable, downloadComplete, setNext | updateAvailable, updateDownloaded | updateAvailable fires on the check, before the download. |
noNeedUpdate, updateCheckResult | the sync() result, getState().lastCheck | |
downloadFailed | updateFailed | |
updateFailed | updateRolledBack | It fires at the start that follows the rollback. |
majorAvailable, breakingAvailable | sync() resolves skipped with device_incompatible | |
channelPrivate | sync() resolves failed with channel_unknown | |
appReady, appReloaded, set, updaterEvent | none | notifyReady() says whether this start follows a rollback. |
The Capacitor SDK's API page has every method, event and type. On the web every method rejects with Capacitor's UNIMPLEMENTED error, where Capgo answered unavailable, so guard the calls with Capacitor.isNativePlatform().
Before and after
The CapacitorUpdater block leaves capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
plugins: {
CapacitorUpdater: {
autoUpdate: 'atBackground',
defaultChannel: 'production',
},
},
};
export default config;hotcodepush.json takes its place at the project root. Capgo applied an update when the app went to the background, while the default next_start waits for a cold start. The closest moment is next_resume with no minimum background time:
{
"appId": "<your app id>",
"channel": "production",
"applyStrategy": "next_resume",
"nextResumeMinimumBackgroundSeconds": 0
}notifyAppReady() goes, and the import takes its place in the entry file:
import { CapacitorUpdater } from '@capgo/capacitor-updater';
import '@hotcodepush/capacitor-live-updates';
CapacitorUpdater.notifyAppReady(); What differs
- Testers switch on the device: Capgo's console overrides and forced device mappings have no counterpart. A tester calls
setChannel()or opens a link, as Switch channels by link sets up, andrelease create --device <id>targets one device. - The fingerprint replaces the version comparison: no
--disable-auto-updateand no--min-update-version. A release never lands on a build whose native code differs, and a--binary-versionrange narrows it to store versions. A release older than the binary is never served. See Compatibility. - Rollouts are deterministic: a device's bucket is a hash of its id and the release id, not a random pick cached for 30 days. Auto-pause runs on every release, 5 percent over 50 applied updates by default. See Roll out gradually.
- Rollback is a release:
release rollbackpublishes an earlier bundle as a new release, mandatory by default.release revokeends a release, andchannel pauseis Capgo's unlink. See Roll back a release. - Deltas need no flag: a device fetches the files it lacks as one pack, with nothing to set per upload or channel.
- Debug builds take updates by default, where Capgo had a toggle per channel.
isEnabledInDebugBuilds: falseturns it off per build, and there is no emulator switch. listChannels()has no counterpart: name the channels in the app or serve them from your backend.
Next steps
- Roll out gradually: start a release at a small percentage, let it progress and let auto-pause stop a bad one.
- Sign your bundles covers the key
initoffered, and the store build that refuses an unsigned release. - Test with preview channels gives every pull request an expiring channel and a QR code for testers, in place of
get-qr. - Capacitor SDK reference documents every
hotcodepush.jsonkey, method, event, type and reason of the SDK you installed.
Last updated on
Migrate from Capawesome Cloud
Decide whether to stay, move or combine, then replace the Capawesome plugin with the HotCodePush Capacitor SDK at your next store release.
Migrate from Codemagic Patch
Move a React Native, Expo or Capacitor app from self-hosted Codemagic Patch to HotCodePush, with every command, key and call mapped, and retire the server.