Migrate from Ionic Appflow
Choose Capawesome Cloud or HotCodePush for your Appflow app, then move Appflow Live Updates to the HotCodePush Capacitor SDK step by step.
An Ionic Appflow app has two places to go: Capawesome Cloud replaces all three things Appflow did, Live Updates, native builds and app store publishing. HotCodePush does over-the-air (OTA) updates alone, for teams that build natively themselves. Appflow stopped taking new customers on 2025-02-11 and serves existing ones until 2027-12-31. What differs, side by side, is on the site's Ionic Appflow alternative page.
Which product fits
Choose Capawesome Cloud when you used Appflow's native builds or its store publishing, or want one console for all three again. Capawesome's guide Migrating from Ionic Appflow imports an Appflow organization export with one CLI command.
Choose HotCodePush when your native builds run in your own pipeline and the OTA updates are your reason to move. It adds a readiness gate with automatic rollback, rollouts with auto-pause, attribute targeting and the fingerprint in place of hand-set native versions. Which of the two products is for you compares them, and the two also combine (Capawesome Cloud).
The rest of this page moves the updates to HotCodePush.
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 Ionic Appflow to HotCodePush: install the skills with `npx skills add hotcodepush-team/skills`, then run the hotcodepush-migrate skill, following https://hotcodepush.com/docs/migrate/ionic-appflow.The agent removes the Appflow SDK, runs init, maps channels, config keys and SDK calls, and reports a checklist for the store release. It never touches Appflow, 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, or a Cordova app on the Appflow Cordova SDK. The minimum versions are on Supported versions.
- You have your own native build pipeline: HotCodePush builds no binaries, so an app that built on Appflow needs a place to build, such as Capawesome Cloud.
- A store release lies ahead, and Node.js is on every build machine, since the build step runs the CLI from an Xcode phase and a Gradle task.
Steps
If you already removed the Appflow SDK and ran init, skip to step 3.
1. Remove the Appflow SDK
npm uninstall @capacitor/live-updatesDelete the LiveUpdates block from capacitor.config.ts, IonAppId and IonChannelName from Info.plist, ionic_app_id and ionic_channel_name from strings.xml, and public_key.pem from the native projects if Secure Live Updates was on. Replace every call per the mapping below, drop live-update generate-manifest from the pipeline, and run npx cap sync.
A Cordova app runs cordova plugin remove cordova-plugin-ionic and removes the APP_ID, CHANNEL_NAME, UPDATE_METHOD, MAX_STORE and MIN_BACKGROUND_DURATION preferences from config.xml.
2. 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. In a Cordova app it installs @hotcodepush/cordova-code-push, which brings its own build step. The Capacitor SDK's Installation page shows the manual steps for a project init could not edit.
It asks whether to generate a signing key, which Sign your bundles recommends, and whether to publish a first release: decline the 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 5, after step 4 built them.
Then import the package in the app's entry file, the one that runs at every start, even when no call follows:
import '@hotcodepush/capacitor-live-updates';The import tells the SDK the app rendered. An app that imports the package late, or not at all, confirms no update, and every one is rolled back. The Cordova plugin needs no import, since window.HotCodePush exists once deviceready has fired.
3. Create the channels your destinations had
production exists. Each further Appflow channel becomes one here, with a lowercase name without spaces:
npx hotcodepush channel create --name staging --discoverable--discoverable lets the app switch to the channel by name with setChannel({ name }), as Channels describes. Without it the channel is reachable by its id alone. A build flavour that follows staging sets HOTCODEPUSH_CHANNEL=staging in its build environment, as Set up environments and tenants shows.
4. 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, and npx hotcodepush binary list shows the registered builds.
5. Release from your pipeline
npm run build && npx hotcodepush release create --bundle-version "$(git rev-parse --short HEAD)" --yesWith HOTCODEPUSH_TOKEN and, for a signing app, HOTCODEPUSH_SIGNING_KEY in the environment, this is the whole pipeline step. The command reads webDir from capacitor.config, or packages www on Cordova. Ship every commit is the pipeline this line 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.
6. Run both until the fleet moves
A binary carries one SDK: an Appflow deploy never reaches a HotCodePush build, and the reverse. Keep deploying to the Appflow channel as long as the old builds have users: Appflow's Live Update MAU shows how many still check in. Stop the Appflow deploys once the old builds are below the share you leave behind.
What maps to what
| Appflow | HotCodePush | Note |
|---|---|---|
| App, an 8-character id | App, appId in hotcodepush.json | init creates it. |
Live Update channel, such as Production | Channel, production | Names are one to 64 characters of letters, digits, - and _. |
| Web build deployed to a channel | Release | release create uploads the web build and releases it in one step. |
appflow deploy web --destination Production | release create --channel production | |
appflow build web | your own npm run build | The CLI runs no build. |
live-update generate-manifest | nothing | The upload hashes every file itself. |
live-update set-native-versions | the fingerprint, on every release, or --binary-version ">=2.3.0" for a range | See Compatibility. |
| "Roll back to here" on the Destinations page | release rollback --channel production --to-release 12 | It creates a new release of that bundle, mandatory by default. See Roll back a release. |
plugins.LiveUpdates.appId and channel in capacitor.config.ts | appId and channel in hotcodepush.json | The build step writes the file into the app. |
IonAppId, IonChannelName, ionic_app_id, ionic_channel_name | nothing | The resource file the build step writes replaces them. |
autoUpdateMethod: 'background' | checkStrategy: auto with applyStrategy: next_start | Both are defaults, so the file names neither. |
autoUpdateMethod: 'none' | checkStrategy: manual | sync() starts each cycle. |
sync() before the splash screen hides | applyStrategy: immediate | The reload waits for the app's first render. |
strategy: 'differential' or 'zip', maxVersions | no setting | There is one pack per update, and the SDK keeps the current, the next and the fallback release. |
enabled: false for development | isEnabledInDebugBuilds: false | Capacitor debug builds take updates by default. |
key: 'public_key.pem', Secure Live Updates | publicKeys in hotcodepush.json, from signing-key create | See Sign your bundles. |
sync(), then reload() on activeApplicationPathChanged | sync(), which reloads itself when it applies | The result reads applied. |
setConfig({ channel }), resetConfig() | setChannel({ name }), setChannel(null) | The choice is persisted. |
getConfig() | getChannel(), getDevice() | |
reload() | applyUpdate() | It reloads only when an update waits. |
IonicCordova.deploy.sync(), configure({ channel }) and the config.xml preferences | window.HotCodePush.sync(), setChannel(), hotcodepush.json | The Cordova SDK is the same API after deviceready. |
IONIC_TOKEN | HOTCODEPUSH_TOKEN |
The Capacitor SDK's API page has every method, event and type, and its Configuration page every key of hotcodepush.json.
The LiveUpdates block leaves capacitor.config.ts:
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
plugins: {
LiveUpdates: {
appId: '042a1261',
channel: 'Production',
autoUpdateMethod: 'background',
maxVersions: 2,
},
},
};
export default config;hotcodepush.json takes its place at the project root. The defaults equal Appflow's background method, so two keys are the whole file:
{
"appId": "<your app id>",
"channel": "production"
}What differs
- The fingerprint replaces the hand-set native versions: it rides on every release, so a release never lands on a build whose native code differs. A
--binary-versionrange over the store version narrows it. See Compatibility. - The readiness gate has no off switch: a release that has not rendered within
readyTimeoutSeconds, 10 by default, is rolled back and marked failed there. It is never tried there again. - A release can reach a share of a channel:
--rollout-percentstarts a release small,--progressionwidens it and auto-pause halts one whose failure rate climbs. Conditions on--attributeand--devicetarget devices. See Roll out gradually and Target devices with attributes. - Channel names are lowercase and spaceless, and a channel id is unguessable: a channel not flagged discoverable is private to the devices holding its id.
- Rollback is a release:
release rollbackpublishes an earlier bundle as a new release, mandatory by default, andrelease revokeends a release outright.
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: generate a key pair and have every store build refuse an unsigned release.
- Capacitor SDK reference documents every
hotcodepush.jsonkey, method, event, type and reason of the SDK you installed. - Capawesome Cloud runs native builds and store publishing beside the OTA updates from HotCodePush.
Last updated on
Migrate from Hot Updater
Move a React Native or Expo app from self-hosted Hot Updater to HotCodePush, map every command and call, and retire the bucket and the database.
Migrate from RevoPush
Move a React Native or Expo app from RevoPush to HotCodePush with the CodePush guide, plus the rows and gotchas where RevoPush differs.