Skip to content

README

This page mirrors the GitHub README. For the full docs, use the sidebar.

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.

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


src-tauri/Cargo.toml
[dependencies]
tauri-plugin-hotswap = "0.0.4"
Terminal window
npm install tauri-plugin-hotswap-api

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) reads plugins.hotswap from tauri.conf.json and requires it.
  • init_with_config(context, config) and HotswapBuilder are programmatic paths; plugins.hotswap in JSON is optional for these.
src-tauri/src/lib.rs
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");

In src-tauri/capabilities/default.json:

{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"hotswap:default"
]
}
import { checkUpdate, applyUpdate, notifyReady } from 'tauri-plugin-hotswap-api';
// ✅ Confirm current version works (call on every startup)
await notifyReady();
// 🔍 Check for updates
const 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 runtime
await configure({ channel: 'beta' });

FeatureDescription
🔐 Signed bundlesEvery download is verified with minisign before extraction
↩️ Auto-rollbackIf notifyReady() isn’t called, the next launch rolls back automatically
📡 ChannelsRoute users to production, staging, beta — switchable at runtime via configure()
🔑 Custom headersAuth tokens, API keys — sent on every check and download request
🔄 Retry with backoffFailed downloads retry automatically (1s → 2s → 4s → 8s)
🔀 Download/activate splitDownload now, apply later — you control the timing
📊 Lifecycle eventshotswap://lifecycle events for telemetry (Sentry, PostHog, etc.)
📏 Bundle size + mandatory flagWarn users on mobile data, force security patches
🌍 Platform-awareSends platform, arch, channel on every check request
🛡️ Size limitsConfigurable max bundle size prevents memory exhaustion
🔒 HTTPS enforcedNon-HTTPS URLs rejected by default
Atomic operationsTemp dir extraction + rename; temp file pointer writes
🤖 Custom resolversHotswapResolver trait — bring your own update source
📦 Zip supportEnable with features = ["zip"]

DocumentDescription
Design PhilosophyOpinionated defaults, extensible when you need it
ConfigurationAll config options, builder API, tauri.conf.json reference
API ReferenceFull JS and Rust API with examples
Server ContractWhat your update endpoint needs to return
SecurityThreat model, mitigations, signing guide
ArchitectureHow the plugin works internally
Creating BundlesBuild, sign, upload your frontend bundles
CONTRIBUTINGHow to contribute to this project
CHANGELOGVersion history

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.


MIT OR Apache-2.0 (same as Tauri)