For most Cordova apps, the day-to-day changes — a copy fix, a styling tweak, a JavaScript bug — live entirely in the web assets. Yet shipping one traditionally means rebuilding the native binary, resubmitting to the App Store and Google Play, and waiting out review before anyone sees it.
Live updates remove that round trip. You publish a new web bundle, and devices download and apply it on their next launch — usually within minutes, with no store submission in the loop. This guide walks through how that works in a Cordova app, the decisions that shape your setup, and a working example you can clone today.
> This post is also available on the Capawesome blog. I work on Capawesome, which builds the Cordova Live Update plugin discussed below — it's open source.
What is a live update?
A Cordova app has two layers. The native layer is the compiled binary from the store — the WebView, the plugins, the platform glue. The web layer is everything inside that WebView: your HTML, CSS, JavaScript, and assets.
A live update is an over-the-air (OTA) update of the web layer only. The native binary stays untouched. Because nothing native changes, you don't need an app review and users don't have to do anything.
The one rule that matters: you can only update what already exists in the native binary. A copy change, a CSS tweak, a web framework upgrade — all fine. Adding or upgrading a Cordova plugin, or anything touching native code, AndroidManifest.xml, or Info.plist — that still needs a real store release.
A nice detail: it works with the stock WebView
If you're coming from Ionic Appflow, here's a welcome difference. The Cordova Live Update plugin works with the stock Cordova WebView. It does not require cordova-plugin-ionic-webview.
It hooks into Cordova's official scheme handlers — https://localhost/ on Android via WebViewAssetLoader, app://localhost/ on iOS via WKURLSchemeHandler. The only thing to avoid is forcing the legacy file scheme (AndroidInsecureFileModeEnabled set to true, or Scheme set to file), which bypasses the handlers the plugin relies on.
Setup
Create an app in Capawesome Cloud:
npx @capawesome/cli apps:create --type cordova
Install the plugin with the app ID it returns:
cordova plugin add @capawesome/cordova-live-update --variable APP_ID=00000000-0000-0000-0000-000000000000
Configure the rest through config.xml preferences:
Picking an update strategy
The AUTO_UPDATE_STRATEGY preference has two values, background and none. Around them you can build four patterns.
Background is the simplest — zero app-side code. The plugin checks on app start and resume, downloads in the background, and applies on the next cold start. There's a built-in 15-minute minimum between checks so it can't turn into a battery-draining poll loop.
Always Latest (recommended) keeps the background download but prompts the user to apply as soon as the bundle is staged. You opt in with the nextBundleSet event:
document.addEventListener("deviceready", () => {
cordova.plugins.LiveUpdate.addListener("nextBundleSet", async ({ bundleId }) => {
if (!bundleId) {
return;
}
const shouldReload = confirm("A new version is available. Install it now?");
if (shouldReload) {
await cordova.plugins.LiveUpdate.reload();
}
});
});
The download already happened in the background, so there's no network wait when the user taps "install".
Force Update blocks app start until the latest bundle is applied — reserve it for cases where stale code is genuinely unsafe. Instant uses a silent push notification to tell the app to check right now; it's a break-glass tool for live incidents, not an everyday flow.
Matching bundles to native versions
The most common production failure with live updates is shipping a web bundle that doesn't match the installed binary — for example, a bundle that calls a plugin method only added in a later native version. If it reaches an older binary, the app breaks on launch.
You prevent it by binding bundles to native versions. The cleanest pattern is one channel per native version: read the version code at runtime and pass the channel directly to sync():
const { versionCode } = await cordova.plugins.LiveUpdate.getVersionCode();
await cordova.plugins.LiveUpdate.sync({ channel: `production-${versionCode}` });
Every deployment is then addressed to a specific native version by name, so a bundle can't land on a binary that can't run it. For a single-channel setup, you can also just set DEFAULT_CHANNEL in config.xml.
Automatic rollbacks
This is the single most important safety net. Two preferences do the work:
If your app doesn't call ready() within READY_TIMEOUT milliseconds of starting, the plugin assumes the new bundle is broken and reverts on the next launch. AUTO_BLOCK_ROLLED_BACK_BUNDLES stops that bundle from ever being re-downloaded (it has no effect unless READY_TIMEOUT is greater than 0).
On the app side, call ready() as early as you can — right after deviceready:
document.addEventListener("deviceready", async () => {
await cordova.plugins.LiveUpdate.ready();
});
As soon as your bootstrap code runs without crashing, call ready(). The whole mechanism only works if you do.
Code signing
Whatever code you ship runs in your app's context, so a tampered bundle means code execution inside your app. Code signing adds authenticity and integrity on top of HTTPS with a standard RSA keypair.
Generate the keypair, sign uploads with the private key, and add the public key to config.xml:
npx @capawesome/cli apps:liveupdates:generatesigningkey
npx @capawesome/cli apps:liveupdates:upload --private-key private.pem
From there, the app verifies every downloaded bundle and refuses anything that isn't signed by your key. Worth enabling in production, especially with multiple channels or self-hosting.
Shipping an update
Once it's wired up, a release is two commands:
npm run build
npx @capawesome/cli apps:liveupdates:upload --channel production
Devices on that channel pick it up on their next launch, download it in the background, and prompt the user to apply it. You can also roll out gradually — --rollout-percentage 10 reaches 10% of the channel, and you widen it once the dashboards look clean.
Try it
The official Cordova Live Update Demo app is a small, dependency-free Cordova app that shows the plugin downloading and applying an OTA bundle on a real device. Clone it, point it at your own Capawesome Cloud app, and watch an update land before wiring up anything in your own codebase.
If you're migrating off Appflow, the concepts map closely — channels stay channels, and the background/none strategies have direct equivalents. The full guide, including the migration path, is on the Capawesome blog.
Questions or feedback? I'd genuinely like to hear how your Cordova OTA setup goes — drop a comment.