Live Updates, also known as Over-the-Air (OTA) or hot code updates, are a way to push updates to your app without going through the app store review process. This is particularly useful for bug fixes or minor updates that don’t require a full app release.
A live-update bundle is executable application code. Use only an update service and plugin configuration that verifies the authenticity and integrity of downloaded bundles, protect publishing credentials, and test rollback behavior. Restrict updates to web assets; native code, plugins, permissions, and other binary changes require a signed store release. Confirm that your update policy complies with the requirements of every store where the app is distributed.
OTA updates must comply with the rules of every target store and must not replace native code, native dependencies, permissions, or behavior that requires store review. Review the current Apple, Google Play, and plugin-provider requirements before enabling live updates in production.
Capgo
Capgo provides the open-source @capgo/capacitor-updater plugin. It supports both a free self-hosted workflow and a managed cloud service.
Capgo Installation
Navigate to the Capacitor project directory and install the plugin:
cd src-capacitorpnpm add @capgo/capacitor-updaterThen synchronize the native projects:
npx cap syncCapgo Configuration
For a self-hosted manual update flow, disable automatic updates in your capacitor.config file. This keeps the update decision in your application code while you host the update bundle and metadata on your own infrastructure.
import { defineCapacitorConfig } from '@quasar/app-vite/capacitor'
export default defineCapacitorConfig({
plugins: {
CapacitorUpdater: {
autoUpdate: 'off'
}
}
})After configuring the plugin, synchronize the Capacitor project again:
npx cap syncCapgo Usage
Call notifyAppReady() after the application has loaded successfully. This confirms that the current bundle works and prevents an unnecessary rollback.
import { CapacitorUpdater } from '@capgo/capacitor-updater'
await CapacitorUpdater.notifyAppReady()In manual mode, your application checks your own update endpoint, downloads the bundle, and decides when to activate it. The following example expects the endpoint to return version, url, and checksum fields:
import { CapacitorUpdater } from '@capgo/capacitor-updater'
const sync = async () => {
const response = await fetch('https://example.com/api/mobile-update')
if (!response.ok) return
const update = await response.json()
if (!update?.url || !update?.version || !update?.checksum) return
const bundle = await CapacitorUpdater.download({
url: update.url,
version: update.version,
checksum: update.checksum
})
await CapacitorUpdater.next({ id: bundle.id })
}The downloaded bundle will be used on the next application start. To apply it immediately after your own confirmation UI or maintenance screen, reload the application:
await CapacitorUpdater.reload()Capgo Publishing updates
Return to the Quasar project root and create the Capacitor web bundle:
quasar build -m capacitor -T [android|ios] --skip-pkgThen use the Capgo CLI to create a bundle archive from src-capacitor/www:
npx @capgo/cli@latest bundle zip [appId] --path src-capacitor/www --jsonUpload the generated archive to your HTTPS server or storage bucket. Your update endpoint should return its metadata:
{
"version": "1.0.1",
"url": "https://example.com/mobile-updates/1.0.1.zip",
"checksum": "sha256-checksum-returned-by-the-capgo-cli"
}Replace [appId] with the application ID from your Capacitor configuration. The archive must contain index.html at its root. The Capgo CLI handles the expected bundle structure and returns the checksum used to verify the download. See the Capgo self-hosted documentation for the complete server workflow.
If you do not want to maintain the update API, storage, channels, rollbacks, and analytics yourself, the same plugin can use the managed Capgo Cloud service.
Capawesome Cloud
The Capawesome Live Update plugin is open source and can download bundles from any URL. Capawesome Cloud is the managed service that adds channels, gradual rollouts, automatic rollbacks, and code signing on top of it. This guide uses Capawesome Cloud.
Capawesome Installation
To enable Live Updates in your Quasar Capacitor app, you need to install the @capawesome/capacitor-live-update plugin. First, navigate to your Capacitor project directory:
cd src-capacitorThen, install the plugin:
pnpm add @capawesome/capacitor-live-updateAfter that, you need to sync the changes with your native projects:
npx cap syncThe latest plugin version requires Capacitor 8. For Capacitor 7, install @capawesome/capacitor-live-update@v7-lts; for Capacitor 6, install @capawesome/capacitor-live-update@v6-lts.
If you submit to the App Store, add the plugin’s entry to your iOS privacy manifest.
Capawesome Configuration
Next, you need to configure the plugin to work with Capawesome Cloud. Create an app in the Capawesome Cloud Console and copy its App ID. Then set the appId in your capacitor.config file, together with the recommended settings:
import { defineCapacitorConfig } from '@quasar/app-vite/capacitor'
export default defineCapacitorConfig({
plugins: {
LiveUpdate: {
appId: '00000000-0000-0000-0000-000000000000',
autoUpdateStrategy: 'background',
autoBlockRolledBackBundles: true,
readyTimeout: 10000
}
}
})Replace 00000000-0000-0000-0000-000000000000 with your actual App ID from the Capawesome Cloud Console. The App ID is not the same as the app identifier (e.g. com.example.app).
With autoUpdateStrategy: 'background', the plugin checks for updates on app start and resume, downloads them in the background, and applies them on the next launch, without any extra code. readyTimeout and autoBlockRolledBackBundles enable automatic rollback: if the app does not report itself as ready within 10 seconds after an update, the plugin reverts to the bundle built into the installed native app and autoBlockRolledBackBundles blocks the broken bundle from being installed again.
After configuring the plugin, sync your Capacitor project again:
npx cap syncCapawesome Usage
For the rollback to work, call ready() as early as possible at app start, before any other plugin method, to signal that the bundle started successfully:
import { LiveUpdate } from '@capawesome/capacitor-live-update'
await LiveUpdate.ready()Updates are applied on the next app start. To offer the update as soon as it is downloaded, listen for the nextBundleSet event and call reload(). Note that reload() restarts the web view and discards the in-memory state of the app, so ask the user first:
import { LiveUpdate } from '@capawesome/capacitor-live-update'
LiveUpdate.addListener('nextBundleSet', async ({ bundleId }) => {
if (!bundleId) return
if (confirm('A new version is available. Install it now?')) {
await LiveUpdate.reload()
}
})See update strategies for the alternatives, including checking for updates manually with sync().
Capawesome Publishing updates
A Live Update bundle is the build output of your web app. In Quasar, this is the src-capacitor/www folder. Create it by running the following command from the root of your project:
quasar build -m capacitor -T [android|ios] --skip-pkgThen log in to Capawesome Cloud with the Capawesome CLI and follow the prompts:
npx @capawesome/cli loginOnce you are logged in, upload the bundle:
npx @capawesome/cli apps:liveupdates:upload --path src-capacitor/wwwThe CLI asks which app to upload to and publishes the bundle to the app’s default channel. Congratulations! You have successfully published your first live update.
To test it, install a build of your app on a device, make a visible change to your web app, rebuild, and upload a new bundle with the same command. Force-close and restart the app, wait about 15 to 30 seconds for the download, then restart it again to see the change. Do not test with quasar dev -m capacitor, since live reload loads the web app from the dev server instead of the installed bundle.
A Live Update can only change the web layer and must stay compatible with the native binary installed on the device. A bundle that relies on a plugin or native change the installed app does not have will crash the app on launch. Once you ship more than one native version, publish each bundle to a channel that matches the native version, as described in Make Updates Version-Compatible.
Capawesome Signing bundles
Code signing lets the app verify that every bundle was produced by you and was not modified in transit, so it refuses anything else. Generate an RSA key pair with the CLI:
npx @capawesome/cli apps:liveupdates:generatesigningkeyThis creates private.pem and public.pem. Keep the private key out of version control and add the public key, printed by the command without line breaks, to the plugin configuration:
export default defineCapacitorConfig({
plugins: {
LiveUpdate: {
// ...
publicKey:
'-----BEGIN PUBLIC KEY-----MIGf...IDAQAB-----END PUBLIC KEY-----'
}
}
})From then on, sign every upload with the private key:
npx @capawesome/cli apps:liveupdates:upload --path src-capacitor/www --private-key private.pemSee Sign Your Bundles for details.
Capawesome Channels and rollouts
Pass --channel to publish to a specific channel, for example a staging channel for testers, and --rollout-percentage to release to a share of devices first and raise it later from the Console:
npx @capawesome/cli apps:liveupdates:upload --path src-capacitor/www --channel production --rollout-percentage 10To publish from a CI pipeline, create an API token, store it as a secret in your CI provider, and log in with it before uploading. Pass --app-id and --yes so the commands run without prompts:
npx @capawesome/cli login --token $CAPAWESOME_TOKEN
npx @capawesome/cli apps:liveupdates:upload --path src-capacitor/www --app-id 00000000-0000-0000-0000-000000000000 --yesFeel free to check out the documentation of the Live Update plugin to see what else you can do with it.