Introduction to the Updater Plugin
Learn what the Tauri Updater plugin is, why auto-updates matter, how the update workflow operates, and how to set up code signing and basic configuration
Desktop applications that never change are rare. Users expect bug fixes, security patches, and new features to arrive without hunting down a download page and reinstalling manually. The updater plugin in Tauri v2 makes that possible. It gives your app a built-in, secure pipeline for checking, downloading, and installing updates, all without the user ever leaving the app window. Application Updates and Auto Updates cover the same pipeline from the distribution side.
This page explains what the plugin is, the security model it relies on, and the overall workflow an update follows from endpoint query to a relaunched application.
What the Updater Plugin Does
The updater plugin is a Rust crate (tauri-plugin-updater) paired with a JavaScript API (@tauri-apps/plugin-updater). Together they let your Tauri desktop application contact a remote endpoint, ask "is there a newer version?", and if the answer is yes, fetch and install that version.
The plugin handles the entire update lifecycle:
- Querying a JSON endpoint that describes the latest release
- Comparing the remote version with the app's current version
- Downloading the appropriate binary package for the user's OS and architecture
- Verifying a cryptographic signature to prove the package is genuine
- Replacing the current application files and triggering a relaunch
You provide the update server (or a static JSON file), the signing keys, and a few lines of configuration. The plugin does the rest.
Why Auto-Updates Are a Requirement, Not a Luxury
Skipping auto-updates in a production desktop app creates real problems quickly.
Security patches arrive too late. If your app has a vulnerability, every day a user runs an old version is a risk. Automatic updates shrink that window to near zero.
Support costs multiply. Users on older versions report bugs you already fixed. You spend time diagnosing issues that a simple update would resolve, and the user walks away frustrated.
Feature adoption crawls. New features sit unused because a fraction of your user base will ever manually download a new installer. With automatic updates, your entire audience gets the improvements.
Version fragmentation makes debugging harder. If your server-side APIs change, you must support multiple client versions or force upgrades. Auto-updates keep the installed base consistent.
Desktop apps that don't auto-update effectively ask their users to act as system administrators. The updater plugin removes that expectation.
How the Update Workflow Operates
Every update check follows the same sequence of events. The plugin orchestrates these steps internally, but understanding the order matters when you need to debug a failed update or design your backend.
1. The app queries an endpoint
When your app calls check(), the plugin sends an HTTP request to each URL listed in the endpoints array inside tauri.conf.json. The request includes the current app version, the OS target, and the CPU architecture as variables in the URL. The endpoint must return a JSON document describing the latest version, or a 2xx status with an empty body if no update is available. If the first endpoint fails or returns an error status, the plugin tries the next one.
2. The plugin compares versions
The JSON response contains a version field following Semantic Versioning rules. The plugin reads the app's current version from tauri.conf.json and determines whether the remote version is newer. By default, older versions are ignored unless you explicitly allow downgrades via CheckOptions.
3. The user is prompted (or the app proceeds automatically)
After check() resolves with an Update object, your frontend code decides what happens next. You can show a dialog, silently download in the background, or skip depending on the situation. The plugin gives you the building blocks; the user experience is yours to design.
4. The update package downloads
Calling download() or downloadAndInstall() fetches the binary artifact from the URL provided in the endpoint JSON. The plugin supports progress events so you can show a progress bar. The file is saved to a temporary location.
5. The signature is verified
Before a single byte of the downloaded package touches the installed application, the plugin checks its signature against the public key embedded in your configuration. This step cannot be skipped. If the signature doesn't match, the update is rejected entirely.
6. The app is replaced and relaunched
Once the signature checks out, the plugin replaces the current executable or app bundle with the new version. On Windows this is handled by the MSI or NSIS installer's built-in update logic; on macOS and Linux the plugin performs the file swap directly. After the replacement, the app must restart. The relaunch() function from the Process Plugin handles this cleanly.
The plugin never forces an update unprompted:
check() only returns information. It never downloads or installs anything on its own. Your frontend code always decides whether to proceed. This means you can add user-facing checks, defer updates, or implement entirely silent upgrades depending on your design.
The Security Model — Why Signing Cannot Be Disabled
Distributing executable code to user machines over the network is a high-stakes operation. If an attacker intercepts or spoofs an update, they gain arbitrary code execution on every user's device. The updater plugin addresses this with mandatory cryptographic signing.
The system uses a public/private key pair:
- Private key: Used during your build process to sign the update artifacts. You must keep this secret, stored in environment variables or a secure secrets manager, never committed to version control.
- Public key: Embedded in
tauri.conf.jsonand shipped with every copy of your app. The plugin uses it at runtime to verify that the downloaded update was signed by the matching private key.
When you run tauri build with the signing environment variables set, Tauri generates .sig signature files next to each binary. The update JSON you publish must include the content of these signature files. When the plugin downloads an update, it checks the signature against the public key. A mismatch means the file was tampered with or corrupted, and the plugin discards it.
Losing the private key is irreversible:
If you lose your private key (or its password), you cannot sign new updates. Existing installations that have your public key baked in will reject any unsigned package forever. Those users will need to manually download and install a fresh copy of your app. Store the key securely from day one.
Never share the private key:
The public key is safe to distribute. The private key must never appear in source code, logs, or CI build outputs. Use encrypted secrets in your CI environment and restrict access.
Generating Your Signing Keys
The Tauri CLI includes a signer generate command that creates a key pair. Run it once per application.
npm run tauri signer generate -- -w ~/.tauri/myapp.key
You'll be prompted to enter a password. This password encrypts the private key at rest, adding a second layer of security.
The command produces two files:
~/.tauri/myapp.key— The encrypted private key.~/.tauri/myapp.key.pub— The public key (plain text).
Successful key generation:
If the terminal prints Your keypair was generated successfully and shows the paths to both files, the keys are ready to use.
Open myapp.key.pub and copy its entire contents. You'll paste this string into the pubkey field of your updater configuration.
Setting Up the Plugin in Your Project
Adding the updater plugin involves changes on both the Rust side and the frontend side. The steps below assume you have a working Tauri v2 project with a React + Vite frontend.
Add the Rust dependency
The plugin must be compiled into your Tauri backend. Run this command in the src-tauri directory:
cargo add tauri-plugin-updater --target 'cfg(any(target_os = "macos", windows, target_os = "linux"))'
The --target flag ensures the crate is only included on desktop platforms. The updater plugin does not apply to mobile builds.
Install the JavaScript package
The frontend needs the JavaScript bindings to call the plugin from your React code. Use your package manager:
npm install @tauri-apps/plugin-updater
You will also likely need @tauri-apps/plugin-dialog for user prompts and @tauri-apps/plugin-process for relaunching. Install those as well if you plan to build a full update UI.
Initialize the plugin in your Rust code
Open src-tauri/src/lib.rs and register the plugin in the Builder setup:
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.setup(|app| {
#[cfg(desktop)]
app.handle().plugin(tauri_plugin_updater::Builder::new().build())?;
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
The #[cfg(desktop)] guard is important. It prevents the plugin from initializing on mobile targets where it isn't supported.
Declare required capabilities
Tauri v2 uses a capability-based permission system. Open src-tauri/capabilities/main.json (or your default capability file) and ensure these permissions are present:
{
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"updater:default",
"updater:allow-check",
"updater:allow-download-and-install"
]
}
Without these permissions, calls to the updater API will be silently blocked at runtime.
Configure the updater in tauri.conf.json
Add the plugins.updater section to your Tauri configuration, and set bundle.createUpdaterArtifacts to true:
{
"bundle": {
"createUpdaterArtifacts": true
},
"plugins": {
"updater": {
"pubkey": "CONTENT OF YOUR myapp.key.pub FILE",
"endpoints": [
"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
]
}
}
}
The createUpdaterArtifacts setting tells the Tauri bundler to generate .sig signature files alongside your normal build output. pubkey must be the raw string from your .pub file, not a file path. The endpoints array accepts URLs with dynamic variables that the plugin substitutes at runtime:
{{current_version}}— The app version fromtauri.conf.json{{target}}— One oflinux,windows, ordarwin{{arch}}— One ofx86_64,aarch64,i686, orarmv7
A common misconfiguration:
A frequent mistake is using the file path to the public key instead of its contents. The pubkey field expects the literal key string. Using "~/.tauri/myapp.key.pub" will cause every signature verification to fail at runtime.
How the Plugin Finds Updates — Endpoints and Static Files
The updater plugin does not talk to a predetermined server. It relies entirely on the endpoints array you provide. There are two common approaches:
Dynamic server. You run a backend (for example, a FastAPI or Node.js service) that receives the request, inspects the {{current_version}}, {{target}}, and {{arch}} variables, and returns a JSON response tailored to that specific request. This lets you implement canary releases, phased rollouts, or platform-specific logic server-side.
Static JSON file. You host a JSON document on a CDN, GitHub Gist, GitHub Release, or S3 bucket. The endpoint URL points directly to the raw file. The JSON must describe every platform you support, with signatures and download URLs for each. This is simpler to set up and works well for open-source projects and small teams.
A minimal static JSON looks like this:
{
"version": "1.0.1",
"notes": "Fixed a critical bug in the export feature.",
"pub_date": "2026-07-08T10:30:00Z",
"platforms": {
"windows-x86_64": {
"signature": "Content of myapp-setup.nsis.zip.sig",
"url": "https://github.com/you/app/releases/download/v1.0.1/myapp-setup.nsis.zip"
},
"darwin-x86_64": {
"signature": "Content of myapp.app.tar.gz.sig",
"url": "https://github.com/you/app/releases/download/v1.0.1/myapp.app.tar.gz"
},
"linux-x86_64": {
"signature": "Content of myapp.AppImage.tar.gz.sig",
"url": "https://github.com/you/app/releases/download/v1.0.1/myapp.AppImage.tar.gz"
}
}
}
The plugin downloads the JSON, extracts the entry matching the user's current platform and architecture, and proceeds with the version comparison.
TLS is enforced in production:
In release builds, the plugin refuses to connect to endpoints that do not use HTTPS. If you are testing locally or on a private network, you can set dangerousInsecureTransportProtocol to true in the configuration, but never ship that to users.
Build Artifacts — What the Bundler Creates for You
When you run tauri build with createUpdaterArtifacts enabled and the signing environment variables set, the build process produces files designed to be uploaded to your release:
- On Linux: an
.AppImage(and optionally a.AppImage.tar.gz) plus a corresponding.sigfile. - On macOS: a
.appbundle compressed into a.app.tar.gzplus a.sigfile. - On Windows: the
.msior.nsisinstaller, and depending on the artifact format, either a direct.sigof the installer or a.zipcontaining the installer plus its.sig.
The exact output depends on the createUpdaterArtifacts value:
true(v2 mode) produces a.sigdirectly for the installer/AppImage."v1Compatible"(for migration) wraps the binary in a compressed archive and signs the archive.
These signature files contain the signature you paste into the latest.json or return from your dynamic server.
Summary
You now understand what the updater plugin does, why signing is mandatory, and how the end-to-end workflow connects.