Skip to content

The App Update Delivery Guide for Capacitor

Keeping your users on the latest version of your app isn't just nice to have — it's critical for security, performance, and delivering the best experience. In this guide, you'll learn how to build a complete update delivery strategy for your Capacitor app by combining two plugins: the App Update plugin for native app store updates and the Live Update plugin for Over-The-Air (OTA) updates.

Understanding the Two Types of Updates

Before diving in, it helps to understand the two fundamentally different ways you can update a Capacitor app:

  1. Native App Updates — Full binary updates distributed through the Google Play Store or Apple App Store. These are required whenever you make changes to native code, add or remove plugins, or update native dependencies. Users typically have to download the new version from the store.

  2. Over-The-Air (OTA) Updates — Also known as live updates, these let you push changes to the web layer of your app (HTML, CSS, and JavaScript) directly to your users' devices. No app store review, no waiting for users to manually update.

The best update strategy uses both: native updates for binary changes, and OTA updates for everything else. This gives you the speed and flexibility of live updates while still being able to ship native changes when needed.

Checking for Native App Updates

The App Update plugin lets you check whether a newer version of your app is available in the app store and prompt users to update. This is especially useful for critical updates or when you need users to move off an older native version.

To install the plugin, refer to the Installation section in the plugin documentation.

Getting Update Information

Use getAppUpdateInfo(...) to check whether an update is available:

import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update';

const checkForUpdate = async () => {
  const info = await AppUpdate.getAppUpdateInfo();
  if (info.updateAvailability === AppUpdateAvailability.UPDATE_AVAILABLE) {
    // An update is available
  }
};

This method returns details like the current and available version, update priority, and whether immediate or flexible updates are allowed on Android.

Android: In-App Updates

On Android, the App Update plugin supports Google Play's in-app update flows, giving you two options.

Immediate Updates

An immediate update displays a full-screen experience that blocks the app until the update is installed. Use this for critical updates that users shouldn't skip:

import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update';

const performImmediateUpdate = async () => {
  const info = await AppUpdate.getAppUpdateInfo();
  if (info.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) {
    return;
  }
  if (info.immediateUpdateAllowed) {
    await AppUpdate.performImmediateUpdate();
  }
};

Flexible Updates

A flexible update downloads the new version in the background while the user continues using the app. Once downloaded, you can prompt the user to restart:

import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update';

const startFlexibleUpdate = async () => {
  const info = await AppUpdate.getAppUpdateInfo();
  if (info.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) {
    return;
  }
  if (info.flexibleUpdateAllowed) {
    await AppUpdate.startFlexibleUpdate();
  }
};

const completeFlexibleUpdate = async () => {
  await AppUpdate.completeFlexibleUpdate();
};

You can monitor the download progress using the addListener('onFlexibleUpdateStateChange', ...) method.

iOS: Opening the App Store

iOS doesn't support in-app update flows like Android. Instead, you can check for an available update and direct users to the App Store using openAppStore(...):

import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update';

const promptUpdate = async () => {
  const info = await AppUpdate.getAppUpdateInfo();
  if (info.updateAvailability === AppUpdateAvailability.UPDATE_AVAILABLE) {
    await AppUpdate.openAppStore();
  }
};

You might want to show a dialog before opening the App Store to let users know why the update is recommended.

Delivering OTA Updates with Live Update

While native updates handle binary changes, most of your day-to-day updates — bug fixes, UI tweaks, new features in the web layer — can be delivered instantly using the Live Update plugin.

To install the plugin, refer to the Installation section in the plugin documentation.

What Are Live Updates?

Live updates let you push changes to your app's web assets (HTML, CSS, JavaScript) directly to users' devices without going through the app store. When your app launches, it checks for new bundles, downloads them, and applies them on the next restart.

Keep in mind that live updates only work for web layer changes. If you add a new Capacitor plugin, update native dependencies, or modify native code, you need a full native update through the store.

Configuration

The Live Update plugin supports a background auto-update strategy that handles everything for you. Configure it in your capacitor.config.json:

{
  "plugins": {
    "LiveUpdate": {
      "appId": "your-capawesome-cloud-app-id",
      "autoUpdateStrategy": "background",
      "readyTimeout": 10000,
      "autoBlockRolledBackBundles": true
    }
  }
}

Here's what each option does:

  • autoUpdateStrategy — Setting this to "background" tells the plugin to automatically check for, download, and stage the latest bundle at app startup and whenever the app resumes after more than 15 minutes. The update is applied on the next app restart.
  • readyTimeout — Gives your app 10 seconds (10000 ms) to signal that it loaded successfully. If the app doesn't call ready() within this time, the plugin automatically rolls back to the previous working bundle. This is your safety net against broken updates.
  • autoBlockRolledBackBundles — When set to true, the plugin automatically blocks bundles that caused a rollback. This prevents the same broken update from being downloaded and applied again.

Versioned Channels

When you release a new native version of your app, the live update bundles from the previous native version might not be compatible. Versioned channels solve this by automatically tying each native version to its own update channel.

Instead of setting a static channel in your Capacitor config, configure it natively so it includes your build version:

Android (android/app/build.gradle):

android {
    defaultConfig {
        resValue "string", "capawesome_live_update_default_channel", "production-" + defaultConfig.versionCode
    }
}

iOS (ios/App/App/Info.plist):

<key>CapawesomeLiveUpdateDefaultChannel</key>
<string>production-$(CURRENT_PROJECT_VERSION)</string>

With this setup, an app with version code 10 automatically receives updates from the production-10 channel, while version 11 receives updates from production-11. This ensures that each native version only gets compatible live updates.

Signaling App Readiness

When using readyTimeout, you need to call ready() early in your app's startup to tell the plugin that the app loaded successfully:

import { LiveUpdate } from '@capawesome/capacitor-live-update';

const signalReady = async () => {
  const result = await LiveUpdate.ready();
  if (result.rollback) {
    console.log('App was rolled back to the default bundle.');
  }
};

Call this as soon as your app has initialized and is in a working state. If you don't call ready() within the configured timeout, the plugin assumes the update is broken and rolls back automatically.

Putting It All Together

With both plugins in place, your update strategy looks like this:

  1. Live updates run automatically in the background. The background strategy handles checking, downloading, and staging new bundles without any additional code.
  2. At app startup, signal readiness. Call ready() to confirm the current bundle works and prevent unnecessary rollbacks.
  3. Check for native updates. Use the App Update plugin to prompt users when a new native version is available in the store.

Here's an example of how this comes together in your app's initialization:

import { Capacitor } from '@capacitor/core';
import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update';
import { LiveUpdate } from '@capawesome/capacitor-live-update';

const initializeApp = async () => {
  await LiveUpdate.ready();
  await checkForNativeUpdate();
};

const checkForNativeUpdate = async () => {
  const info = await AppUpdate.getAppUpdateInfo();
  if (info.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) {
    return;
  }
  if (Capacitor.getPlatform() === 'android') {
    if (info.immediateUpdateAllowed) {
      await AppUpdate.performImmediateUpdate();
    }
  } else {
    await AppUpdate.openAppStore();
  }
};

With this setup, your web layer updates are delivered silently through live updates, while native updates are handled through in-app prompts. Your users always get the latest version — whether it requires a store download or not.

Try Capawesome Cloud Free

Conclusion

By combining the App Update and Live Update plugins, you can build a solid update delivery strategy that covers every scenario. Use live updates for fast, friction-free web layer changes, and the App Update plugin to nudge users toward new native versions when needed.

If you want to learn more about how live updates work under the hood, check out the blog post How Live Updates for Capacitor Work. Join the Capawesome Discord server if you have questions or want to connect with the community, and subscribe to the Capawesome newsletter to stay updated on the latest news.