HotCodePush

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.

Install the HotCodePush skills:

npx skills add hotcodepush-team/skills

Then 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 init creates 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-fleet

A 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-updater

Delete 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 init

init 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:

src/main.ts
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)" --yes

This 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

CapgoHotCodePush
npx @capgo/cli init <apikey>npx hotcodepush init
login, --apikey, CAPGO_TOKENlogin, HOTCODEPUSH_TOKEN
app add, app list, app deleteapp create, app list, app delete
app debugdevice probe, and showDebugScreen() on the device
app settingEdit hotcodepush.json and rebuild.
bundle upload --channel productionrelease create --channel production
bundle upload without --channelbundle upload
bundle upload -c production,betarelease 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, --tusNothing 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-incompatibleThe 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 cleanupbundle list and bundle delete stay, while cleanup is retention.
channel add beta --self-assignchannel create --name beta --discoverable
channel add --defaultNone. The build's channel picks it.
channel set --bundle 1.2.3release create --bundle 17, or release rollback --to-release 12
channel set --disable-auto-update majorNothing. The fingerprint decides, or --binary-version for a range of store versions.
channel set --rollout-pause, --rollout-resumerelease pause, release resume
channel set --rollout-promoterelease rollout --rollout-percent 100
channel set --rollout-rollbackrelease 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-foundchannel delete --ignore-not-found
channel currentBundlerelease list --channel beta
Unlink, on the channel pagechannel pause
Built-in Bundle, on the channel pagerelease revoke --all
key create, key savesigning-key create writes publicKeys itself.
get-qrA 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.CapacitorUpdaterhotcodepush.jsonNote
appIdappIdThis is the HotCodePush app id, not the bundle id. init writes it.
defaultChannelchannel, or HOTCODEPUSH_CHANNEL per buildThe build step resolves it to the channel's id.
autoUpdate: 'atBackground', the defaultapplyStrategy: next_resume with nextResumeMinimumBackgroundSeconds: 0This is the closest moment, while the default next_start waits for a cold start.
autoUpdate: 'always', directUpdate: 'always'applyStrategy: immediateThe reload waits for the app's first render.
autoUpdate: 'atInstall', 'onLaunch'no exact matchimmediate applies at any check.
autoUpdate: 'onlyDownload'applyStrategy: manualapplyUpdate() finishes the update.
autoUpdate: 'off'checkStrategy: manualsync() starts each cycle.
periodCheckDelaycheckIntervalSeconds, 900 by default, at least 60Capgo checks on every foreground. Here a resume checks when the last check is older than the interval.
appReadyTimeout: 10000readyTimeoutSeconds: 10It is in seconds, with no off switch.
autoDeleteFailed, autoDeletePrevious, resetWhenUpdatenoneA failed bundle is blocked on the device, and the SDK keeps the current, the next and the fallback release.
publicKeypublicKeysThese are signature keys, from signing-key create.
versionnoneThe build step reads the store build's version.
autoSplashscreen, keepUrlPathAfterReloadnone
updateUrl, statsUrl, channelUrl, allowModifyUrl, the local* keysnone
shakeMenu, allowShakeChannelSelectorshowDebugScreen() behind a gesture of your ownIt shows state only, with no channel picker.
the nativeUpdatePrompt* keysnone

The Capacitor SDK's Configuration page lists every key with its type and default.

SDK calls

CapgoHotCodePushNote
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 nextResumeMinimumBackgroundSecondsThere 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()noneThe 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 methodsnone
the download event, percent from 0 to 100downloadProgress, progress from 0 to 1
updateAvailable, downloadComplete, setNextupdateAvailable, updateDownloadedupdateAvailable fires on the check, before the download.
noNeedUpdate, updateCheckResultthe sync() result, getState().lastCheck
downloadFailedupdateFailed
updateFailedupdateRolledBackIt fires at the start that follows the rollback.
majorAvailable, breakingAvailablesync() resolves skipped with device_incompatible
channelPrivatesync() resolves failed with channel_unknown
appReady, appReloaded, set, updaterEventnonenotifyReady() 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:

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:

hotcodepush.json
{
  "appId": "<your app id>",
  "channel": "production",
  "applyStrategy": "next_resume",
  "nextResumeMinimumBackgroundSeconds": 0
}

notifyAppReady() goes, and the import takes its place in the entry file:

src/main.ts
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, and release create --device <id> targets one device.
  • The fingerprint replaces the version comparison: no --disable-auto-update and no --min-update-version. A release never lands on a build whose native code differs, and a --binary-version range 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 rollback publishes an earlier bundle as a new release, mandatory by default. release revoke ends a release, and channel pause is 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: false turns 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 init offered, 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.json key, method, event, type and reason of the SDK you installed.

Last updated on

On this page