Updater Plugin
Learn to configure and use the Tauri v2 updater plugin to ship automatic updates with signed artifacts and a React frontend
The updater plugin lets your Tauri application check for, download, and install new versions without requiring users to visit a website. A user who opened your app yesterday can receive a critical patch today without touching a download page. The same distribution story is covered in Application Updates and Auto Updates.
The plugin communicates with a server you control (or a static JSON file) and compares the currently running version to what the server reports. If a newer release exists, your frontend can download the signed package, install it, and restart the app to apply the update. All of this runs inside Tauri's security boundary — updates are cryptographically signed, and no unsigned package can be installed.
How the Updater Works
The updater follows a strict, verifiable pipeline:
- Your app sends a request to one or more update endpoints, passing the current version, operating system, and CPU architecture.
- The server responds with a JSON document containing the latest version number, release notes, and download URLs.
- The plugin compares versions. If the server version is newer (or downgrades are explicitly allowed), an
Updateobject is returned to the frontend. - The frontend can call
downloadAndInstall(). The plugin downloads the package, verifies its signature against a public key embedded intauri.conf.json, and installs it. - After installation, the app must be relaunched. The old executable is replaced, and the user starts the new version.
The signature verification is the linchpin. Without it, any server impersonator could deliver a malicious binary. Tauri enforces this — you cannot disable signature checking. Every update must be signed with a private key that only you possess.
Installation
The updater plugin is not bundled with Tauri's core. You need to add the Rust crate and the JavaScript bindings separately. The Updater Plugin introduction walks through signing keys and endpoint configuration before you write frontend code.
Desktop Only:
The updater works on macOS, Windows, and Linux. It does not support Android or iOS because those platforms enforce their own distribution and update mechanisms through app stores.
Adding the Rust Plugin
Use the Tauri CLI to add the plugin automatically. It updates Cargo.toml, registers the plugin in lib.rs, and adds the required permissions.
npm run tauri add updater
If you prefer to add dependencies manually, include the crate in src-tauri/Cargo.toml and initialize it in src-tauri/src/lib.rs.
# Only available on desktop, so use a platform-specific dependency
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
tauri-plugin-updater = "2"
#[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)] attribute ensures the plugin is only registered on desktop targets. The setup closure is the correct place to install plugins that require access to the app handle.
Adding the JavaScript Bindings
Install the npm package from your frontend project root.
npm install @tauri-apps/plugin-updater
Enabling Permissions
The updater plugin requires explicit permissions to function. Open src-tauri/capabilities/main.json (or the capability file you use for your main window) and add the updater permissions.
{
"identifier": "main",
"description": "permissions for the main window",
"local": true,
"windows": ["main"],
"permissions": [
"updater:default",
"updater:allow-check",
"updater:allow-download-and-install"
]
}
updater:default includes a set of common permissions. The specific allow-check and allow-download-and-install permissions grant the frontend the ability to call check() and downloadAndInstall(). Without these, the JavaScript functions will reject with a permission error.
Missing Permissions Cause Runtime Errors:
If the permissions are absent, calling check() from the frontend will not work. Tauri will reject the command, and the returned promise will throw. Always verify your capability file after adding a plugin.
Signing Updates
Every update package must be cryptographically signed. The signature proves that the binary came from you and was not altered in transit. Tauri's updater uses a public/private key pair — the private key signs the bundle during the build, and the public key is embedded in the app to verify the signature before installation.
Generating the Key Pair
Run the Tauri CLI's signer command. It creates two files: a private key (.key) and a public key (.key.pub). You will be prompted to set a password to encrypt the private key.
npm run tauri signer generate -- -w ~/.tauri/myapp.key
On Windows, use $HOME/.tauri/myapp.key or an absolute path. After running, you will have:
~/.tauri/myapp.key— private key (keep secret, never commit to version control)~/.tauri/myapp.key.pub— public key (safe to share, embedded in the app)
Losing the Private Key Is Irreversible:
If you lose the private key or its password, you cannot sign new updates. Users with the current public key embedded in their installed app will never be able to install any new version you release. Store the private key and its password in a secure, backed-up location, preferably a secrets manager in your CI/CD pipeline.
Configuring the Public Key
Copy the entire content of myapp.key.pub into the pubkey field under plugins > updater in tauri.conf.json.
{
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk...",
"endpoints": [
"https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
]
}
}
}
The pubkey must be the raw string contents of the public key file, not a file path.
Setting Environment Variables for Builds
When you run tauri build, the bundler needs access to the private key to sign the update artifacts. Set two environment variables in your terminal or CI environment:
export TAURI_SIGNING_PRIVATE_KEY="Path or content of your private key"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="your-password"
TAURI_SIGNING_PRIVATE_KEY accepts either the file path to the .key file or the raw content of the key. The password variable is only required if you set a password during key generation. If you leave it empty and no password was set, signing still works.
.env Files Are Ignored:
Tauri does not read .env files during the build process. You must export the environment variables directly in the shell session or through your CI/CD secrets configuration.
Updater Artifacts Created During Build
With createUpdaterArtifacts set to true in the bundle configuration, running tauri build produces additional files alongside your normal installers:
- Linux:
myapp.AppImage(reused) andmyapp.AppImage.sig(signature). - macOS:
myapp.app.tar.gz(updater bundle) andmyapp.app.tar.gz.sig. - Windows:
myapp-setup.exe/myapp.msi(reused) and corresponding.sigfiles.
If you are migrating from Tauri v1 and have existing users, set createUpdaterArtifacts to "v1Compatible" instead of true. This produces .tar.gz archives on Linux and .zip archives on Windows, matching the v1 format. Once all users are on v2, switch to true.
{
"bundle": {
"createUpdaterArtifacts": true
}
}
The v1Compatible Option Is Temporary:
The "v1Compatible" value will be removed in Tauri v3. Switch to true as soon as your user base has migrated to v2.
Tauri Configuration Reference
The plugins.updater section in tauri.conf.json controls the updater's behavior. The following table outlines every key.
| Key | Type | Description |
|---|---|---|
pubkey | string | The public key content (not a file path) generated by tauri signer generate. |
endpoints | array of strings | URLs the updater contacts to check for updates. TLS is enforced in production. Tauri tries each URL in order until a response with a 2xx status is received. |
dangerousInsecureTransportProtocol | boolean | Set to true to allow non-HTTPS endpoints. Only use for local development; never in production. |
windows.installMode | string | Controls how Windows installers present the update. One of "passive" (default, progress bar, no interaction), "basicUi" (requires user interaction), or "quiet" (no feedback, no admin elevation). |
Dynamic URL Variables
Each endpoint URL can contain three placeholder variables that Tauri replaces at runtime:
{{current_version}}— the version of the app making the request (fromtauri.conf.jsonversion field).{{target}}— the operating system:linux,windows, ordarwin.{{arch}}— the CPU architecture:x86_64,i686,aarch64, orarmv7.
A typical endpoint might look like:
https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}
Your server can parse these path segments to determine which platform-specific package to return.
Windows installMode in Detail
"passive": The installer shows a small progress bar and completes without user clicks. This is the default and recommended mode for most apps."basicUi": A full installer UI appears. The user must click through the wizard. Useful if you need to display license changes or require explicit acceptance."quiet": No UI at all. The installer cannot request administrator privileges, so it only works for per-user installations or if the app already runs elevated. Generally not recommended.
Checking for Updates from the Frontend
The JavaScript API lives in @tauri-apps/plugin-updater. The primary function is check(). It contacts the configured endpoints, compares versions, and returns either an Update object or null. Checking for Updates is the dedicated frontend walkthrough.
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
import { ask, message } from '@tauri-apps/plugin-dialog';
async function checkForUpdates(onUserClick: boolean = false) {
const update = await check();
if (!update) {
if (onUserClick) {
await message('You are on the latest version.', {
title: 'No Update Available',
kind: 'info',
});
}
return;
}
const yes = await ask(
`Version ${update.version} is available.\n\n${update.body ?? ''}`,
{
title: 'Update Available',
kind: 'info',
okLabel: 'Update',
cancelLabel: 'Later',
}
);
if (yes) {
await update.downloadAndInstall();
await relaunch();
}
}
The check() function returns null when the app is current. The update object contains version (string), body (release notes, optional), date (optional), and methods like download(), install(), and downloadAndInstall(). The deprecated available property should not be used; always test for null instead.
Do Not Use update.available:
The available property on the Update object is deprecated and may be removed in a future release. Always check whether check() returned null to determine if an update exists.
Check Options
You can pass an options object to check() to control the request:
| Option | Type | Description |
|---|---|---|
headers | HeadersInit | Custom headers sent to the update endpoint. |
timeout | number | Request timeout in milliseconds. |
proxy | string | Proxy URL used for checking and downloading updates. |
target | string | Override the target platform string sent to the server. |
allowDowngrades | boolean | Allow installing a version older than the current one. Defaults to false. |
Integrating with React
In a React application, check for updates when the app starts. Use useEffect with an empty dependency array to run once.
import { useEffect } from 'react';
import { check } from '@tauri-apps/plugin-updater';
import { relaunch } from '@tauri-apps/plugin-process';
import { ask } from '@tauri-apps/plugin-dialog';
function App() {
useEffect(() => {
async function checkForUpdates() {
try {
const update = await check();
if (!update) return;
const yes = await ask(
`Update to ${update.version} is available!\n\n${update.body ?? ''}`,
{ title: 'Update Available', kind: 'info', okLabel: 'Update', cancelLabel: 'Cancel' }
);
if (yes) {
await update.downloadAndInstall();
await relaunch();
}
} catch (error) {
console.error('Updater error:', error);
}
}
checkForUpdates();
}, []);
return <h1>Hello, Tauri!</h1>;
}
export default App;
The dialog and process plugins are used here to ask the user and restart the app. You must also install @tauri-apps/plugin-dialog and @tauri-apps/plugin-process and add their permissions in the capability file.
{
"permissions": [
"dialog:default",
"dialog:allow-ask",
"dialog:allow-message",
"process:allow-restart",
"updater:default",
"updater:allow-check",
"updater:allow-download-and-install"
]
}
Everything Is Wired Up:
When you see the dialog appearing on startup and correctly reporting "No Update Available" or prompting to install, the updater pipeline is fully functional. The public key is embedded, endpoints are reachable, and the signature verification chain is ready.
Downloading and Installing Updates
The Update object provides three methods: download(), install(), and the combined downloadAndInstall(). Each returns a promise.
download(onEvent?, options?)— fetches the update package and stores it temporarily. The optionalonEventcallback receives progress events (DownloadEvent) containingdownloadedandcontentLengthfields.install()— installs the previously downloaded package. The signature is verified during this step.downloadAndInstall(onEvent?, options?)— downloads and immediately installs in one call. This is the simplest and most common approach.
const update = await check();
if (update) {
await update.downloadAndInstall((event) => {
if (event.event === 'Progress') {
console.log(`Downloaded ${event.data.downloaded} of ${event.data.contentLength} bytes`);
}
});
await relaunch();
}
Signature Verification Happens at Install Time:
If the downloaded package's signature does not match the embedded public key, the install step will fail with an error. The updater will not install any unsigned or tampered package. This is a security guarantee and cannot be bypassed.
Release Management and Server Support
The updater plugin can work with two types of backends: a static JSON file hosted on any web server or a dynamic server that computes the response. Release Management covers versioning, signed artifacts, and hosting.
Static JSON File
A static file is the simplest approach. Host a JSON file (e.g., latest.json) at a known URL. The file must contain at least version and platforms:
{
"version": "1.2.0",
"notes": "Bug fixes and performance improvements.",
"platforms": {
"darwin-x86_64": {
"signature": "...",
"url": "https://releases.myapp.com/1.2.0/myapp_x86_64.app.tar.gz"
},
"darwin-aarch64": {
"signature": "...",
"url": "https://releases.myapp.com/1.2.0/myapp_aarch64.app.tar.gz"
},
"linux-x86_64": {
"signature": "...",
"url": "https://releases.myapp.com/1.2.0/myapp_amd64.AppImage"
},
"windows-x86_64": {
"signature": "...",
"url": "https://releases.myapp.com/1.2.0/myapp_x64.msi"
}
}
}
Tauri automatically selects the correct platform object based on the running OS and architecture. The signature fields contain the .sig file content generated during the build. The url points to the downloadable package.
Dynamic Server Endpoints
A dynamic server receives the path segments from the endpoint URL (including {{target}}, {{arch}}, {{current_version}}) and can compute the correct response. For instance, a server at https://releases.myapp.com/ might implement logic:
- Look up the latest release for the given target and arch.
- If the
current_versionis older, return a JSON response with the newer version, notes, and download URLs. - If the current version is the latest, respond with
204 No Contentor404 Not Found, which tells the updater that no update is available.
A static JSON approach is easier to reason about and works well with GitHub Releases, where you can attach a latest.json file to each release.
GitHub Releases Integration
Many projects use GitHub Releases as the distribution hub. During your CI workflow (e.g., GitHub Actions), build the app for all platforms, sign the artifacts, and upload them to a release draft. Attach a latest.json that references the uploaded binaries.
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
tagName: v__VERSION__
releaseName: "App v__VERSION__"
releaseBody: "See the assets to download this version and install."
includeUpdaterJson: true
releaseDraft: true
prerelease: false
When includeUpdaterJson is true, the action generates a latest.json with the correct signatures and URLs pointing to the release assets. The latest.json is then available at a predictable URL like:
https://github.com/your-org/your-repo/releases/latest/download/latest.json
This URL can be placed directly in your endpoints array.
Check Rate Limits:
GitHub's release download endpoint is subject to rate limiting. For apps with a large user base, consider proxying the request through your own server or a CDN to avoid hitting the limit.
Common Mistakes
Mistakes when configuring the updater often surface as silent failures — the app reports no update when one exists, or the download succeeds but the signature verification fails. Here are the most frequent pitfalls.
- Embedding a file path instead of the key content in
pubkey. Thepubkeyfield expects the literal string contents of the public key file. Passing a path like"~/.tauri/myapp.key.pub"will cause every update signature check to fail. - Forgetting to set
createUpdaterArtifacts. Without"createUpdaterArtifacts": truein the bundle config, Tauri does not produce the.sigfiles. The updater has nothing to verify against, and checks will fail. - Missing environment variables during the build. If
TAURI_SIGNING_PRIVATE_KEYis not exported, the build completes but the update artifacts are unsigned. The updater will reject them. - Not adding the required permissions. The capability file must include
updater:allow-checkandupdater:allow-download-and-install. A missing permission results in a runtime error, not a graceful fallback. - Relying on
update.available. This property is deprecated. Always check for anullreturn fromcheck(). - Using non-HTTPS endpoints in production. Tauri enforces TLS for updater endpoints unless
dangerousInsecureTransportProtocolis explicitly set. A plainhttp://endpoint will be rejected.
Best Practices
A smooth update experience respects the user's time and attention.
- Check for updates silently on startup. Don't force an update dialog immediately. Show a non-intrusive notification if an update is available, and let the user decide when to install it.
- Provide release notes. The
bodyfield in the update response appears in the dialog. Write clear, concise notes so users understand what changed. - Allow deferral. Offer a "Later" button. Forcing an update can frustrate users in the middle of important work.
- Show download progress. Pass an event handler to
downloadAndInstall()and update a progress bar. A stalled UI with no feedback makes users think the app crashed. - Secure your private key. Store it in a CI secrets manager, never in version control. Restrict access to the fewest people necessary.
- Test the full pipeline on a staging endpoint before going live. Use a separate endpoint and a pre-release version to verify the update flow end-to-end.
- On macOS, notarize your app. Notarization is separate from updater signing. Without it, Gatekeeper may block the new version after the updater installs it.
Summary
The Tauri updater plugin provides a cryptographically secure, cross-platform update mechanism that integrates directly with your frontend. By generating a key pair, embedding the public key in your configuration, and hosting a static or dynamic endpoint, you can ship updates that are verified from download to installation. The frontend call is a single check() followed by downloadAndInstall(), with full control over the user experience through dialogs and progress callbacks.
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
Checking for Updates
Detect available updates, download them, and install them in your Tauri v2 application using the updater plugin with React and Vite
Release Management
How to manage releases, versioning, and update distribution for Tauri v2 applications using the Updater plugin