---
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-07-17
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 are a powerful feature that allows you to deliver minor bug fixes and updates to your Capacitor app in real time. 
To understand exactly how Live Updates work, it is important to first understand 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>

As you can see from the image above, a Capacitor app consists essentially 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. 
Let's take a closer look into the mechanics of this process in the following sections.

## 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

First, let's take a look at the implementation for 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
}
```

It should be noted that 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, you only need to 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

An important aspect of Live Updates is security. After all, you don't want your app to be compromised by an attacker. 
Therefore, it is crucial that the downloaded files are 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. 
Therefore not even we are capable of manipulating our customers' files, as we do not have access to the private keys. 
Feel free to check out our documentation on [Code Signing](../../cloud/live-updates/code-signing.md).

## Compliance

Now that we understand how Live Updates for Capacitor work, the question arises whether they are compliant with the guidelines of the app stores.
And the answer is **yes**!

### 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?

Because 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.

### If Capawesome Cloud doesn't have my private signing key, how does it still deliver signed updates?

It doesn't sign anything on your behalf — you generate the RSA keypair yourself, sign each bundle locally (or in your own CI) before uploading, and only the public key ever reaches Capawesome Cloud or your app. This means Capawesome Cloud is structurally unable to tamper with your bundles even if its infrastructure were compromised, since it never has access to the key that could produce a validly signed one.

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

Specifically 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

Live Updates are a powerful feature that allows you to deliver updates to your Capacitor app in real time.
In this blog post, we have looked at how Live Updates work in detail and how they can be implemented in Android and iOS from a plugin developer's perspective.
We have also discussed the security and compliance aspects of Live Updates and shown that they are fully compliant with the guidelines of the app stores.

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).
