---
title: "How Capacitor Live Updates Work Under the Hood"
description: A technical deep dive into how Capacitor Live Updates work internally on Android and iOS, including server paths, code signing, and app store compliance.
date: 
  created: 2024-07-22
  updated: 2026-09-07
authors:
  - robingenz
categories:
  - Capacitor
  - Cloud
  - Guides
  - SDKs
links:
  - Capacitor Live Update: sdks/capacitor/live-update.md
faq: true
---

# How Capacitor Live Updates Work Under the Hood

One of the biggest advantages of Capacitor over other runtimes is the ability to deliver updates in real-time, without having to resubmit the app to the app stores.
This feature is referred to as Live Updates or Over-the-Air (OTA) Updates.
This post is a technical deep dive into how Live Updates work internally on Android and iOS, covering server paths, code signing, and app store compliance from a plugin developer's perspective.

If you're looking for a hands-on, implementation-focused guide instead, check out [Capacitor Live Updates: A Complete Guide to OTA Updates](./capacitor-live-updates-guide.md).

<!-- more -->

## Concept

Live Updates let you deliver minor bug fixes and updates to your Capacitor app in real time. 
To understand exactly how Live Updates work, you first need to know the different layers of a Capacitor app and how they interact with each other.

<figure markdown>
  ![Layers](../../assets/images/diagrams/live-update-layers-light.png#only-light)
  ![Layers](../../assets/images/diagrams/live-update-layers-dark.png#only-dark)
  <figcaption>Capacitor App Layers</figcaption>
</figure>

A Capacitor app consists of a web layer and a native layer. 
The web layer consists of the HTML, CSS, and JS files that are loaded into the web view. 
The native layer consists of the native code, such as Java or Swift.

Capacitor now allows you to replace the web layer of the app at runtime since those files are not compiled into the app binary.
For this purpose, the new HTML, CSS, and JS files only need to be downloaded from a server and stored at a specific location in the app's file system. 
The following sections cover the mechanics of this process.

## Implementation

Capacitor provides various interfaces to instruct the WebView which files should be loaded. 
Here we distinguish between the current server path and the next server path.

The current server path is the path to the files that are currently loaded in the WebView. 
Changing the current server path results in a refresh of the WebView.
The files are immediately loaded from the new path and displayed in the WebView.

The next server path, on the other hand, is the path to the files that should be loaded at the next restart of the app. 
Changing the next server path does not result in a refresh of the WebView.

### Android

Under Android, the next server path is stored in the [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences){:target="_blank"}. 
To change the server path, the [`serverBasePath`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/android/capacitor/src/main/java/com/getcapacitor/plugin/WebView.java#L15){:target="_blank"} preference in the [`CapWebViewSettings`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/android/capacitor/src/main/java/com/getcapacitor/plugin/WebView.java#L14){:target="_blank"} preferences file of the Capacitor WebView needs to be updated:

```java
private void setNextCapacitorServerPath(String path) {
  SharedPreferences.Editor webViewSettingsEditor = getContext().getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE).edit();
  webViewSettingsEditor.putString("serverBasePath", path);
  webViewSettingsEditor.commit();
}
```

The current server path, on the other hand, is set directly via the Capacitor Android Bridge. For this, the [`setServerBasePath`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/android/capacitor/src/main/java/com/getcapacitor/Bridge.java#L1374){:target="_blank"} method must be called, and the WebView must be reloaded:

```java
private void setCurrentCapacitorServerPath(String path) {
  getBridge().setServerBasePath(path);
  getBridge().reload();
}
```

To reset the server path to the default value, [`public`](https://github.com/ionic-team/capacitor/blob/ceee68a2db363e9d9a638aa4ed8569fd82d1013a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java#L89){:target="_blank"} must be passed as the path.
This is especially useful when you want to implement a fallback mechanism in case the new files cannot be loaded.

### iOS

Under iOS, the next server path is stored in the [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults){:target="_blank"}. 
For this purpose, Capacitor provides the [`KeyValueStore`](https://github.com/ionic-team/capacitor/blob/7113a198bb8165f69e046fbb0db5c938402367bf/ios/Capacitor/Capacitor/KeyValueStore.swift){:target="_blank"} class, which is used to store key-value pairs in the UserDefaults.
To change the server path, the value for the key [`serverBasePath`](https://github.com/ionic-team/capacitor/blob/7113a198bb8165f69e046fbb0db5c938402367bf/ios/Capacitor/Capacitor/Plugins/WebView.swift#L32){:target="_blank"} must now be updated:

```swift
private func setNextCapacitorServerPath(path: String) {
  KeyValueStore.standard["serverBasePath"] = path
}
```

Only the last path component of the path is relevant. 
This is because Capacitor under iOS only supports server paths that are located in the [`/Library/NoCloud/ionic_built_snapshots`](https://github.com/ionic-team/capacitor/blob/3c7b3333762ce8cf7b7c279437dada1e2de7fbea/ios/Capacitor/Capacitor/CAPBridgeViewController.swift#L84:L87){:target="_blank"} directory.
A valid path would be, for example, `/Library/NoCloud/ionic_built_snapshots/my-bundle`.

The current server path is again set via the Capacitor iOS Bridge. 
To do this, call the [`setServerBasePath`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/ios/Capacitor/Capacitor/CAPBridgeViewController.swift#L267){:target="_blank"} method:

```swift
private func setCurrentCapacitorServerPath(path: String) {
  self.bridge.viewController.setServerBasePath(path: path)
}
```

Also under iOS, the server path can be reset to the default value by passing [`public`](https://github.com/ionic-team/capacitor/blob/7113a198bb8165f69e046fbb0db5c938402367bf/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m#L17){:target="_blank"} as the path.

## Security

Live Updates replace files that the app loads at runtime, so those files must be checked for their authenticity and integrity, also known as code signing.

Authenticity means that the files have not been tampered with and come from a trusted source.
Integrity means that the files have not been corrupted during the download process.

To ensure the authenticity and integrity of the downloaded files, these files must be digitally signed. 
For this, a private key is used to sign the files, and a public key is used to verify the signature. 
The private key must be kept secret and must not be shared with anyone. 
The public key, on the other hand, is stored in the app and used to verify the signature.

This is exactly the security mechanism we use in [Capawesome Cloud](../../cloud/live-updates/index.md) to securely deliver Live Updates. 
Where the private key lives is up to you. Sign bundles locally or in your own CI with the CLI's `--private-key` option, or upload the key as a [web signing certificate](../../cloud/native-builds/certificates/web.md) so that Capawesome Cloud signs bundles for you. Either way, the app verifies every downloaded bundle against the embedded public key before applying it. 
See our documentation on [Code Signing](../../cloud/live-updates/code-signing.md).

## Compliance

Live Updates for Capacitor comply with the guidelines of both the Apple App Store and the Google Play Store.

### Apple App Store

Live Updates are fully compliant with the Apple App Store policies.
The [Apple Developer Program License Agreement](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/){:target="_blank"} states that interpreted code may be downloaded to an application as long as it does not change the primary purpose of the application and does not bypass signing, sandbox, or other security features of the OS:

> Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store, (b) does not create a store or storefront for other code or applications, and (c) does not bypass signing, sandbox, or other security features of the OS.

So as long as you do not change the primary purpose of your app via Live Updates, they are fully compliant with the Apple App Store policies since they only update the web layer of your app.

### Google Play Store

Live Updates are also compliant with the Google Play Policies. 
The third paragraph of [Device and Network Abuse](https://support.google.com/googleplay/android-developer/answer/9888379/){:target="_blank"} states that an app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play's update mechanism. 
However, the same paragraph also states that this restriction does not apply to JavaScript running in a webview or browser:

> This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser).

As Live Updates can only update the web layer of your app, they are fully compliant with the Google Play Policies.

## FAQ

### What's the actual difference between the "current" and "next" server path?

The current server path is what the WebView is loading right now. Changing it triggers an immediate reload, so the user sees the new files right away. The next server path only takes effect on the app's next restart and doesn't touch what's currently displayed. This distinction is why a live update can be silently staged in the background (writing to the next path) without disrupting the user's active session, and only takes visible effect later.

### Why does iOS only care about the last path component of the server path?

Capacitor on iOS restricts valid server paths to subdirectories of `/Library/NoCloud/ionic_built_snapshots`. The framework already assumes that directory as the base, so only the final component (e.g. `my-bundle`) needs to be supplied to identify which snapshot to load. This is a hard platform constraint of Capacitor's iOS bridge, not a configurable option.

### How do I reset a Capacitor app back to its built-in bundle programmatically?

Pass `public` as the server path on both Android and iOS. That's Capacitor's documented convention for "use the bundle shipped inside the native binary" rather than any downloaded bundle, and it's the mechanism a fallback or rollback implementation uses to recover from a broken update.

### Does Capawesome Cloud need my private signing key to deliver signed updates?

No. You can generate the RSA keypair yourself and sign each bundle locally or in your own CI before uploading, so that only the public key is embedded in your app. If you prefer Capawesome Cloud to sign bundles for you, upload the private key as a [web signing certificate](../../cloud/native-builds/certificates/web.md). In both cases the app refuses to apply any bundle whose signature does not match the embedded public key.

### Does Apple's or Google's compliance exception apply to any code I download, or just JavaScript in a WebView?

The exception covers code interpreted in a virtual machine or runtime with indirect API access. JavaScript in a WebView or browser is the explicit example both policies cite. It does not cover downloading and executing native binaries, native libraries, or bytecode that runs outside that sandboxed interpreter, which is why live updates are scoped strictly to the web layer and never touch compiled native code.

## Related Posts

- [Announcing the Live Update Plugin for Capacitor](./announcing-the-capacitor-live-update-plugin.md)
- [Channel Pausing for Capawesome Cloud Live Updates](./capawesome-cloud-channel-pausing.md)
- [Channel Surfing with Capacitor Live Updates](./capawesome-cloud-channel-surfing.md)
- [Capacitor Live Updates: Signing vs Encryption](./capacitor-live-updates-end-to-end-encryption.md)

## Conclusion

Use an existing Live Update plugin instead of implementing the server path handling yourself, unless you are building a plugin of your own.
Check out our [Capacitor Live Update plugin](../../sdks/capacitor/live-update.md) to get started with Live Updates in your app today.
If you want to build a complete update strategy that combines live updates with native app store updates, take a look at [Capacitor App Updates: The Complete Guide](./capacitor-app-update-guide.md).
If you're wondering how Live Updates compare to using `server.url` in production, read [The Right Way to Update Your Capacitor App Remotely](./the-right-way-to-update-your-capacitor-app-remotely.md).
