tauri-plugin-hotswap 0.0.3

Open-source OTA plugin for Tauri v2 — push frontend updates to users without rebuilding the binary. Self-hosted, signed bundles, auto-rollback.
Documentation

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

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

flowchart TD
    A["Your CDN / S3 / any HTTPS host
    manifest.json
    signed frontend.tar.gz"] -- "download + verify signature" --> B
    B["Tauri App

    HotswapAssets::get(key)
    1. filesystem (cached)
    2. embedded (fallback)"]

🚀 Quickstart

1. Install

# src-tauri/Cargo.toml
[dependencies]
tauri-plugin-hotswap = "0.0.1"
npm install tauri-plugin-hotswap-api

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

3. Register the plugin

// 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");

4. Add capability

In src-tauri/capabilities/default.json:

{
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "hotswap:default"
  ]
}

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 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' });

✨ 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

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

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

MIT OR Apache-2.0 (same as Tauri)