Tauri Auto-Updates on Windows: A Practical Guide
What You’ll Learn
- How Tauri’s built-in updater works on Windows
- How to configure update endpoints with a static JSON file
- What code signing means for Windows updates and when you need it
- A minimal update check flow in the frontend
- The mistakes that cost me the most time when I first set this up
Shipping a desktop app is one thing. Keeping it updated is another.
If your users have to manually download a new installer every time you push a fix, most of them will stop updating. The app drifts out of date. Bug reports pile up for issues you already fixed. Support becomes a version management problem.
Tauri has a built-in updater that solves this. But the documentation assumes you already know how update systems work, so the first setup can feel harder than it should be.
This is the guide I wish I had the first time.
How the Tauri Updater Works
The flow is simple:
- Your app checks a URL for update metadata
- If a newer version exists, it downloads the update bundle
- The user confirms the update (or you auto-apply it)
- The app restarts with the new version
On Windows, the update bundle is an .msi or .nsis installer. Tauri downloads it, verifies the signature, runs the installer silently, and restarts the app.
Step 1: Enable the Updater Plugin
In Tauri v2, the updater is a plugin. Add it to your project:
cargo add tauri-plugin-updater
npm install @tauri-apps/plugin-updater
Register the plugin in your Rust entry point:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_updater::Builder::new().build())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Step 2: Configure the Update Endpoint
In tauri.conf.json, add the updater configuration:
{
"plugins": {
"updater": {
"endpoints": [
"https://your-domain.com/updates/latest.json"
],
"pubkey": "YOUR_PUBLIC_KEY_HERE"
}
}
}
The pubkey is used to verify that updates are actually from you. Generate a key pair:
npx tauri signer generate -w ~/.tauri/myapp.key
This creates a private key (for signing builds) and a public key (for verifying updates). Keep the private key safe. Put the public key in tauri.conf.json.
Step 3: Create the Update Manifest
The endpoint returns a JSON file that tells the app what the latest version is and where to download it:
{
"version": "1.2.0",
"notes": "Bug fixes and performance improvements",
"pub_date": "2026-05-25T10:00:00Z",
"platforms": {
"windows-x86_64": {
"signature": "SIGNATURE_STRING_HERE",
"url": "https://your-domain.com/updates/myapp_1.2.0_x64-setup.nsis.zip"
}
}
}
The signature field is generated during the build process when you set the TAURI_SIGNING_PRIVATE_KEY environment variable:
export TAURI_SIGNING_PRIVATE_KEY=$(cat ~/.tauri/myapp.key)
npm run tauri build
After the build, Tauri outputs a .sig file alongside the installer. The contents of that file go into the signature field of your manifest.
Step 4: Host the Update Files
You need to host two things:
- The JSON manifest (
latest.json) - The installer bundle (
.nsis.zip)
For small projects, I use GitHub Releases. Upload the installer and the manifest, and point the endpoint URL to the raw manifest file.
For more control, a simple static file server works fine. Even an S3 bucket or Vercel static hosting works — the updater just needs to fetch a JSON file and download a binary.
Step 5: Check for Updates in the Frontend
On the frontend, trigger the update check and handle the flow:
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
export async function checkForUpdates() {
const update = await check();
if (!update) {
return null;
}
return {
version: update.version,
notes: update.body,
install: async () => {
await update.downloadAndInstall();
await relaunch();
},
};
}
In your React component:
function UpdateBanner() {
const [update, setUpdate] = useState<{ version: string; notes: string; install: () => Promise<void> } | null>(null);
const [installing, setInstalling] = useState(false);
useEffect(() => {
checkForUpdates().then(setUpdate);
}, []);
if (!update) return null;
return (
<div className="update-banner">
<span>Version {update.version} is available</span>
<button
onClick={async () => {
setInstalling(true);
await update.install();
}}
disabled={installing}
>
{installing ? 'Installing...' : 'Update now'}
</button>
</div>
);
}
The user sees a banner, clicks update, and the app restarts with the new version. No manual download needed.
The Mistakes That Cost Me Time
Forgetting to sign the build
If you build without TAURI_SIGNING_PRIVATE_KEY set, there is no signature file. The updater will refuse to install the update because it cannot verify it. Always sign release builds.
Wrong platform key in the manifest
The platform key must match exactly. For Windows 64-bit with NSIS, it is windows-x86_64. Getting this wrong means the updater finds no matching platform and silently reports no update available.
Stale manifest
If you update the installer but forget to update latest.json, users will not see the update. I automate manifest generation as part of my release script.
Not testing the full flow locally
It is tempting to skip testing updates because it requires building an older version first, then building a newer version, hosting the files, and running through the whole flow. Do it anyway. Update bugs are the kind that only show up in production and are painful to debug remotely.
A Simple Release Checklist
- Bump the version in
tauri.conf.jsonandpackage.json - Build with signing enabled
- Upload the installer bundle to your hosting
- Copy the
.sigfile contents intolatest.json - Update the version and download URL in
latest.json - Upload
latest.json - Verify by running the previous version and confirming it detects the update
For mature projects, automate steps 2 through 7 in a GitHub Actions workflow. For early-stage projects, the manual checklist is fine.
Final Thought
Auto-updates are one of those features that feel optional until you need them. Once your app has real users, shipping fixes without asking everyone to re-download is worth the setup time.
Tauri makes this easier than most desktop frameworks, but the first setup still has sharp edges. Hopefully this guide saves you the hours I spent figuring it out.
If you need help building or shipping a Tauri desktop app, take a look at my portfolio: voidcraft-site.vercel.app.