cap-update
v8.0.3
Published
Capacitor native plugin for over-the-air (OTA) live bundle updates using Bridge.setServerBasePath — no local HTTP server needed.
Maintainers
Readme
A lightweight, secure, and high-performance Over-The-Air (OTA) live update solution for Capacitor apps. Download, manage, and apply web bundle updates at runtime without rebuilding or resubmitting your app.
Cap Update leverages Capacitor's native engine to serve web assets directly. This ensures maximum performance, zero port conflicts, and full compatibility with all your existing plugins.
Key Features & Benefits
- 🚀 Native Performance: Uses Capacitor's built-in
Bridge.setServerBasePath()API. No local HTTP server, no extra overhead. - 🔒 Secure by Design: Maintains the
https://localhostorigin. No cleartext traffic issues or CORS headaches. - 🛠️ Seamless Integration: Fully compatible with all Capacitor plugins since it uses the standard platform engine.
- 📦 Atomic Updates: Bundles are downloaded and extracted in the background, ready to be applied instantly or on the next restart.
- 🛡️ App Store Friendly: Uses standard APIs that don't trigger "embedded server" flags during review.
- 🪶 Zero Dependencies: Lightweight footprint on both iOS and Android.
Compatibility
| Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | ---------- | | 8.0.x | >=8.x.x | Supported | | 7.0.x | 7.x.x | Supported |
Install
npm install cap-update
npx cap synciOS
No additional configuration required. The plugin uses SSZipArchive for ZIP extraction (included automatically).
Android
No additional configuration required. Uses Java's built-in java.util.zip — zero extra dependencies.
Quick Start
import { CapUpdate } from 'cap-update';
// Full automated update cycle
const result = await CapUpdate.sync({
url: 'https://your-server.com/api/updates/check',
channel: 'production',
});
if (result.updated) {
console.log('App updated to latest bundle!');
}API Reference
downloadBundle(options)
Download a ZIP bundle from a URL and extract it locally.
await CapUpdate.downloadBundle({
url: 'https://your-server.com/bundles/v1.2.0.zip',
bundleId: 'v1.2.0',
checksum: 'sha256-abc123...', // optional integrity check
});| Param | Type | Description |
|---|---|---|
| url | string | (required) URL of the ZIP bundle |
| bundleId | string | Identifier for this bundle. Auto-generated from URL if omitted |
| checksum | string | SHA-256 checksum for integrity verification |
Returns: Promise<BundleInfo>
setBundle(options)
Set a downloaded bundle as the active bundle. By default, it takes effect on the next app restart. Set immediate: true to reload the WebView immediately.
// Apply on next restart
await CapUpdate.setBundle({ bundleId: 'v1.2.0' });
// Apply immediately (reloads WebView)
await CapUpdate.setBundle({ bundleId: 'v1.2.0', immediate: true });| Param | Type | Default | Description |
|---|---|---|---|
| bundleId | string | — | (required) ID of the downloaded bundle |
| immediate | boolean | false | Reload WebView immediately |
getBundle()
Get info about the currently active bundle.
const info = await CapUpdate.getBundle();
// { bundleId: 'v1.2.0', path: '/data/.../bundles/v1.2.0', status: 'active' }
// { bundleId: 'built-in', status: 'active' } // when using defaultReturns: Promise<BundleInfo>
getBundles()
List all downloaded bundles on the device.
const { bundles } = await CapUpdate.getBundles();
// [{ bundleId: 'v1.1.0', status: 'downloaded' }, { bundleId: 'v1.2.0', status: 'active' }]Returns: Promise<{ bundles: BundleInfo[] }>
deleteBundle(options)
Delete a specific downloaded bundle. Cannot delete the currently active bundle.
await CapUpdate.deleteBundle({ bundleId: 'v1.1.0' });reset(options?)
Reset to the built-in bundle (the one shipped with the app binary).
// Reset and reload immediately
await CapUpdate.reset({ immediate: true });
// Reset on next restart
await CapUpdate.reset();reload()
Reload the WebView. Useful after calling setBundle() without immediate: true.
await CapUpdate.reload();sync(options)
Automated update cycle: check for updates → download → apply → reload.
const result = await CapUpdate.sync({
url: 'https://your-server.com/api/updates/check',
channel: 'production',
});
console.log(result.updated); // boolean
console.log(result.latestBundle); // bundle metadata from server| Param | Type | Default | Description |
|---|---|---|---|
| url | string | — | (required) Update check endpoint URL |
| channel | string | 'production' | Deployment channel |
Returns: Promise<SyncResult>
checkForUpdate(options)
Check if an update is available without downloading it.
const result = await CapUpdate.checkForUpdate({
url: 'https://your-server.com/api/updates/check',
channel: 'staging',
});
if (result.isUpdateAvailable) {
console.log('New version:', result.latestBundle);
console.log('Download from:', result.downloadUrl);
}Returns: Promise<CheckUpdateResult>
Update Server Protocol
The sync() and checkForUpdate() methods send a GET request to your endpoint with these headers:
| Header | Description |
|---|---|
| X-Device-Identifier | Unique device ID |
| X-Platform | android or ios |
| X-Bundle-Id | Currently active bundle ID |
| X-Channel | Deployment channel |
Expected JSON Response
{
"is_update_available": true,
"latest_bundle": {
"id": "v1.2.0",
"version": "1.2.0"
},
"current_bundle": {
"id": "v1.1.0"
},
"download_url": "https://your-server.com/bundles/v1.2.0.zip"
}Types
interface BundleInfo {
bundleId: string;
status: 'active' | 'downloaded' | 'built-in';
}
interface DownloadBundleOptions {
url: string;
bundleId?: string;
checksum?: string;
}
interface SetBundleOptions {
bundleId: string;
immediate?: boolean;
}
interface SyncOptions {
url: string;
channel?: string;
}
interface SyncResult {
updated: boolean;
latestBundle?: any;
}
interface CheckUpdateResult {
isUpdateAvailable: boolean;
latestBundle?: any;
currentBundle?: any;
downloadUrl?: string;
}How It Works
┌─────────────────────────────────────────────────┐
│ Your App (WebView) │
│ Served via Capacitor's built-in local server │
│ URL: https://localhost (unchanged) │
└──────────────────┬──────────────────────────────┘
│
bridge.setServerBasePath(path)
│
┌─────────────┴─────────────┐
│ Default: assets/public │ ← Built-in bundle (APK/IPA)
│ Custom: files/bundles/ │ ← Downloaded OTA bundles
└───────────────────────────┘- Download — ZIP bundle is downloaded and extracted to
{app_files}/cap_update_bundles/{bundleId}/ - Set —
bridge.setServerBasePath(path)tells Capacitor's server to serve from the new directory - Persist — The path is saved so it survives app restarts
- Reload — WebView reloads, now serving the updated assets
No secondary HTTP server. No port management. Just Capacitor doing what it already does — serving files — from a different folder.
License
MIT
