README
This page mirrors the GitHub README. For the full docs, use the sidebar.
What is this?
Section titled “What is this?”An open-source Tauri v2 plugin that pushes OTA frontend updates to users instantly — without rebuilding the native binary, without app store review, and without requiring a cloud service. Self-hosted, bring your own CDN.
It works by swapping Tauri’s embedded asset provider at startup. The WebView keeps loading from tauri://localhost — the swap is invisible. Your keys, your server, your infrastructure. If anything goes wrong, the app rolls back to embedded assets on next launch.
Platform Support
Section titled “Platform Support”| Platform | Supported |
|---|---|
| macOS | ✅ |
| Windows | ✅ |
| Linux | ✅ |
| Android | ✅ |
| iOS | ✅ |
⚠️ App Store / Google Play note: OTA updates that swap frontend assets (HTML, CSS, JS) within a WebView are generally permitted, but policies can change. Review Apple’s App Store Review Guidelines (3.3.2) and Google Play’s Device and Network Abuse policy before shipping to ensure your use case complies with the latest rules.
How it works
Section titled “How it works”🚀 Quickstart
Section titled “🚀 Quickstart”1. Install
Section titled “1. Install”[dependencies]tauri-plugin-hotswap = "0.0.4"npm install tauri-plugin-hotswap-api2. Configure
Section titled “2. Configure”Add to your tauri.conf.json:
{ "plugins": { "hotswap": { "endpoint": "https://your-server.com/api/updates/{{current_sequence}}", "pubkey": "<YOUR_MINISIGN_PUBKEY>" } }}Config source matters:
init(context)readsplugins.hotswapfromtauri.conf.jsonand requires it.init_with_config(context, config)andHotswapBuilderare programmatic paths;plugins.hotswapin JSON is optional for these.
3. Register the plugin
Section titled “3. Register the plugin”pub fn run() { let context = tauri::generate_context!(); // init() consumes the context to swap the asset provider, // then returns the modified context alongside the plugin. let (hotswap, context) = tauri_plugin_hotswap::init(context) .expect("failed to initialize hotswap");
tauri::Builder::default() .plugin(hotswap) .run(context) .expect("error running app");}Programmatic alternative (no plugins.hotswap required in tauri.conf.json):
let context = tauri::generate_context!();let config = tauri_plugin_hotswap::HotswapConfig::new("<YOUR_MINISIGN_PUBKEY>") .endpoint("https://your-server.com/api/updates/{{current_sequence}}");let (hotswap, context) = tauri_plugin_hotswap::init_with_config(context, config) .expect("failed to initialize hotswap");4. Add capability
Section titled “4. Add capability”In src-tauri/capabilities/default.json:
{ "identifier": "default", "windows": ["main"], "permissions": [ "core:default", "hotswap:default" ]}5. Use from the frontend
Section titled “5. Use from the frontend”import { checkUpdate, applyUpdate, notifyReady } from 'tauri-plugin-hotswap-api';
// ✅ Confirm current version works (call on every startup)await notifyReady();
// 🔍 Check for updatesconst result = await checkUpdate();
if (result.available) { // ⬇️ Download, verify, and activate await applyUpdate();
// 🔄 Reload to serve new assets window.location.reload();}That’s it. A few lines to add OTA updates to your Tauri app.
You can also change configuration at runtime — for example, to switch channels without restarting:
import { configure } from 'tauri-plugin-hotswap-api';
// Switch to a beta channel at runtimeawait configure({ channel: 'beta' });✨ Features
Section titled “✨ Features”| Feature | Description |
|---|---|
| 🔐 Signed bundles | Every download is verified with minisign before extraction |
| ↩️ Auto-rollback | If notifyReady() isn’t called, the next launch rolls back automatically |
| 📡 Channels | Route users to production, staging, beta — switchable at runtime via configure() |
| 🔑 Custom headers | Auth tokens, API keys — sent on every check and download request |
| 🔄 Retry with backoff | Failed downloads retry automatically (1s → 2s → 4s → 8s) |
| 🔀 Download/activate split | Download now, apply later — you control the timing |
| 📊 Lifecycle events | hotswap://lifecycle events for telemetry (Sentry, PostHog, etc.) |
| 📏 Bundle size + mandatory flag | Warn users on mobile data, force security patches |
| 🌍 Platform-aware | Sends platform, arch, channel on every check request |
| 🛡️ Size limits | Configurable max bundle size prevents memory exhaustion |
| 🔒 HTTPS enforced | Non-HTTPS URLs rejected by default |
| ⚡ Atomic operations | Temp dir extraction + rename; temp file pointer writes |
| 🤖 Custom resolvers | HotswapResolver trait — bring your own update source |
| 📦 Zip support | Enable with features = ["zip"] |
📖 Documentation
Section titled “📖 Documentation”| Document | Description |
|---|---|
| Design Philosophy | Opinionated defaults, extensible when you need it |
| Configuration | All config options, builder API, tauri.conf.json reference |
| API Reference | Full JS and Rust API with examples |
| Server Contract | What your update endpoint needs to return |
| Security | Threat model, mitigations, signing guide |
| Architecture | How the plugin works internally |
| Creating Bundles | Build, sign, upload your frontend bundles |
| CONTRIBUTING | How to contribute to this project |
| CHANGELOG | Version history |
🛡️ Security
Section titled “🛡️ Security”Every update is cryptographically signed with minisign and verified before extraction. The plugin is designed to fail safely — if anything goes wrong, the app falls back to embedded assets.
See the full Security documentation for the threat model and all mitigations.
License
Section titled “License”MIT OR Apache-2.0 (same as Tauri)