Skip to content

The Updater: Keeping Apps Fresh

Modern users expect software to improve over time without manually downloading installers. Tauri includes a built-in, secure over-the-air (OTA) update system that handles checking for updates, verifying signatures, and installing them.

First, you need to add the updater plugin to your project (cargo add tauri-plugin-updater) and initialize it in your Rust code. Then, configure tauri.conf.json to tell the app where to look for updates.

tauri.conf.json
{
"plugins": {
"updater": {
"endpoints": [
"https://releases.myapp.com/latest.json"
],
"pubkey": "YOUR_PUBLIC_KEY_HERE"
}
}
}

Tauri enforces security by requiring all updates to be digitally signed. This prevents a “Man-in-the-Middle” attack where a server is spoofed to serve malware.

You can generate a keypair using the Tauri CLI:

Terminal window
tauri signer generate -w ~/.tauri/myapp.key

This outputs a public key (safe to share, goes in config) and a private key (keep secret!). During your build process (e.g., GitHub Actions), you must provide the private key via environment variables: TAURI_SIGNING_PRIVATE_KEY and TAURI_SIGNING_PRIVATE_KEY_PASSWORD.

The endpoint defined in your config looks for a JSON file containing version information, release notes, and download links for different platforms (Windows, macOS, Linux). When you run tauri build, the CLI generates not just the installers, but also .sig (signature) files and a JSON manifest.

You can host these files anywhere: Amazon S3, GitHub Pages, or a custom VPS. The URL structure in the JSON must point to the actual location of your binaries.

Finally, you need to tell the app when to check for updates. This is usually done in the frontend when the app launches.

import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
async function checkForAppUpdates() {
const update = await check();
if (update) {
console.log(`found update ${update.version} from ${update.date} with notes ${update.body}`);
let downloaded = 0;
let contentLength = 0;
// Download and install the update
await update.downloadAndInstall((event) => {
switch (event.event) {
case 'Started':
contentLength = event.data.contentLength;
console.log(`started downloading ${contentLength} bytes`);
break;
case 'Progress':
downloaded += event.data.chunkLength;
console.log(`downloaded ${downloaded} from ${contentLength}`);
break;
case 'Finished':
console.log('download finished');
break;
}
});
// Restart the app to apply changes
await relaunch();
}
}