Release Management

How to manage releases, versioning, and update distribution for Tauri v2 applications using the Updater plugin

Setting up the updater plugin so it can check for new versions is only half the story. The other half is producing signed release artifacts, hosting them somewhere your application can reach, and making sure the version numbers line up. This section covers the full release lifecycle: how to version your app, how to build signed update bundles that the updater will accept, and how to publish them so the plugin's check() call actually finds something.

Prerequisites:

This page assumes you have already installed and initialized the updater plugin in your Tauri project. If you haven't, start with the Introduction and Checking for Updates sections of the Updater Plugin chapter. You also need a working Tauri v2 build pipeline (the tauri build command must complete successfully).

Versioning

Every release needs a version number. The updater plugin compares the version embedded in your app against the version advertised by your update server. If the server version is higher, an update is available.

Tauri reads the version from the top-level version field in tauri.conf.json. This is a standard SemVer string: major.minor.patch, optionally with a pre-release suffix like 1.2.0-beta.1.

src-tauri/tauri.conf.json
{
  "productName": "MyApp",
  "version": "1.0.0",
  "identifier": "com.mycompany.myapp",
  "build": {
    "frontendDist": "../dist",
    "devUrl": "http://localhost:1420",
    "beforeDevCommand": "npm run dev",
    "beforeBuildCommand": "npm run build"
  },
  "app": {
    "windows": [
      {
        "title": "MyApp",
        "width": 800,
        "height": 600
      }
    ],
    "security": {
      "csp": null
    }
  },
  "bundle": {
    "active": true,
    "targets": "all",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ]
  },
  "plugins": {
    "updater": {
      "pubkey": "YOUR_PUBLIC_KEY_HERE",
      "endpoints": [
        "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
      ]
    }
  }
}

Only the version field matters for the updater's comparison logic. The productName and identifier do not affect the check, but they must stay consistent across releases because they determine file names and OS-level identities.

SemVer rules are enforced:

The updater expects strict SemVer. Strings like 1.0 or v1.0.0 will cause version parsing to fail silently and no update will be offered. Always use three-part versions (e.g., 1.2.3). A leading v (like v1.2.3) is accepted by Tauri v2, but it's safest to omit it.

Building Signed Updates

The Tauri updater cannot be used without cryptographic signatures. Every update bundle must be signed with a private key, and the matching public key must be embedded in your app's configuration. If the signature check fails — because the file was tampered with, or the private key was lost — the update is rejected.

Generating a Key Pair

Run the following command once. It generates a private key and a public key, protected by a password you choose.

npm run tauri signer generate -- -w ~/.tauri/myapp.key

Two files are created: ~/.tauri/myapp.key (the private key) and ~/.tauri/myapp.key.pub (the public key). The public key is safe to share and goes into tauri.conf.json. The private key must never be committed to version control or distributed.

Losing the private key permanently breaks updates:

If you lose the private key file or the password, you cannot sign future updates. Existing users who have your app installed will be stuck on their current version because you cannot produce a valid signature for a new bundle. Store the key and password in a secrets manager or encrypted vault. A password manager or a CI secret store are both good options.

Configuring Signing for a Build

During the build process, Tauri reads the signing key from environment variables. Set these before running tauri build:

  • TAURI_SIGNING_PRIVATE_KEY — the full path to the .key file, or the key's contents as a string.
  • TAURI_SIGNING_PRIVATE_KEY_PASSWORD — the password you chose (leave empty if you set no password).

On macOS or Linux:

export TAURI_SIGNING_PRIVATE_KEY="~/.tauri/myapp.key"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="your_password_here"

On Windows PowerShell:

$env:TAURI_SIGNING_PRIVATE_KEY = "C:\Users\You\.tauri\myapp.key"
$env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD = "your_password_here"

.env files are ignored:

Tauri does not load .env files for the updater signing keys. You must set these as real environment variables. In CI/CD environments, use the platform's secret management to inject them.

Enabling Updater Artifacts

The bundler needs to know that it should produce files for the updater. Add createUpdaterArtifacts to the bundle section of tauri.conf.json:

src-tauri/tauri.conf.json
{
  "bundle": {
    "createUpdaterArtifacts": true
  }
}

Setting this to true is the standard for new v2 projects. It tells Tauri to produce a .sig signature file alongside each installer, and on macOS/Linux also a compressed archive (.tar.gz) of the app bundle, with its own signature. These signed artifacts are what you upload to your update server.

Migrating from v1 to v2:

If your app was originally distributed with Tauri v1 and you are upgrading to v2, set "createUpdaterArtifacts": "v1Compatible" instead. This produces artifacts that match the v1 structure so existing users get a valid update path. Once all your users are on v2, switch to true.

Build Output per Platform

After running tauri build with the environment variables set, the generated files differ by operating system.

Linux (AppImage):

  • myapp.AppImage — the standard application bundle.
  • myapp.AppImage.sig — the signature.

macOS:

  • myapp.app — the standard application bundle.
  • myapp.app.tar.gz — the updater bundle (compressed).
  • myapp.app.tar.gz.sig — the signature of that compressed bundle.

Windows (NSIS):

  • myapp-setup.exe — the installer.
  • myapp-setup.exe.sig — the signature of the installer.
  • myapp.msi — alternative MSI installer (if enabled).
  • myapp.msi.sig — its signature.

Each .sig file is a small text file containing the cryptographic signature Tauri needs to verify the bundle's authenticity. When you host your update endpoint, you must provide the signature alongside the installer, either by including it in a JSON manifest or by serving both files at predictable URLs.

The Update Endpoint

The updater plugin calls each URL listed in endpoints in sequence until it receives a response. That response tells the app whether a newer version exists, what the new version number is, a download URL, and the signature.

Static JSON Endpoint

The simplest approach is a static JSON file. You host it on any HTTP server that supports plain files — GitHub Releases, S3, a simple CDN, or even a GitHub Gist.

The JSON must contain:

  • version — a SemVer version string (e.g., "1.2.0").
  • notes — release notes shown to the user.
  • platforms — an object keyed by target triple, each containing url and signature.

Here is a minimal example for Windows and macOS:

{
  "version": "1.2.0",
  "notes": "Fixed crash on startup and improved dark mode support.",
  "platforms": {
    "windows-x86_64": {
      "url": "https://releases.myapp.com/1.2.0/myapp-setup.exe",
      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tI..."
    },
    "darwin-x86_64": {
      "url": "https://releases.myapp.com/1.2.0/myapp.app.tar.gz",
      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tI..."
    },
    "darwin-aarch64": {
      "url": "https://releases.myapp.com/1.2.0/myapp.app.tar.gz",
      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tI..."
    },
    "linux-x86_64": {
      "url": "https://releases.myapp.com/1.2.0/myapp.AppImage",
      "signature": "dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tI..."
    }
  }
}

The signature must match exactly:

The signature field must contain the exact content of the .sig file generated by the build — usually a long base64 string. A single missing character or extra newline will cause signature verification to fail and the update to be rejected.

The url in each platform entry must point directly to the updater bundle: the .tar.gz on macOS, the .AppImage on Linux, and the .exe or .msi on Windows. Tauri will download this file, verify it against the signature, and then replace the installed app with it.

Dynamic Endpoint Variables

The updater replaces certain placeholders in the endpoint URLs before making the request. This lets you construct a single endpoint that works for all platforms and versions. The following variables are available:

  • {{current_version}} — the version of the installed app (e.g., 1.0.0).
  • {{target}} — the OS name: linux, windows, or darwin.
  • {{arch}} — the CPU architecture: x86_64, i686, aarch64, or armv7.

A real endpoint configuration might look like this:

src-tauri/tauri.conf.json
{
  "plugins": {
    "updater": {
      "pubkey": "YOUR_PUBLIC_KEY_HERE",
      "endpoints": [
        "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}"
      ]
    }
  }
}

When the app runs check(), it replaces the placeholders and makes a GET request to a URL like https://releases.myapp.com/windows/x86_64/1.0.0. Your server is expected to respond with the same JSON structure shown above, but you can generate it dynamically based on the current version — for example, by checking a database for the latest version and constructing the response.

Multiple endpoints as fallback:

The updater tries each endpoint in order until a non‑200 response is received. If the first URL returns 404 or 500, it moves to the next. This lets you provide a primary CDN and a fallback GitHub Release. TLS is enforced in production builds; you can override this with dangerousInsecureTransportProtocol: true in the updater config, but only do so for local testing.

Serving the Endpoint from GitHub Releases

GitHub Releases provides free CDN-backed hosting and integrates easily with CI. When you use the tauri-apps/tauri-action GitHub Action, it can automatically generate and upload a latest.json file that matches the format the updater expects.

Here is a minimal workflow step that builds, signs, and drafts a release:

.github/workflows/release.yml
- name: Build and publish
  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."
    includeUpdaterJson: true
    releaseDraft: true
    prerelease: false

When includeUpdaterJson is true, the action creates a latest.json file containing the version, notes, and platform-specific download URLs with signatures. That file is attached to the GitHub Release. You can then point your updater endpoint directly to the raw file URL:

https://github.com/<org>/<repo>/releases/latest/download/latest.json

If you see a valid JSON response with a higher version:

Once your endpoint serves the latest.json and your app's check() returns an update, the release management pipeline is wired correctly. The hardest part — key generation, signing, and endpoint wiring — is done.

Windows Install Mode

On Windows, the updater plugin supports an additional configuration to control how the installer behaves during an update. Set it inside plugins.updater.windows.installMode:

src-tauri/tauri.conf.json
{
  "plugins": {
    "updater": {
      "windows": {
        "installMode": "passive"
      }
    }
  }
}

There are three possible values:

  • "passive" — A small window with a progress bar appears. The update installs without requiring user interaction. This is the default and recommended mode for most apps.
  • "basicUi" — A basic installer UI is shown, and the user must click through it to complete the update.
  • "quiet" — No window is displayed at all. The installer cannot request administrator privileges by itself, so this only works if the app is installed per‑user or already runs elevated.

Choose "passive" unless you have a strong reason to show installer prompts. A quiet update that shows only a progress bar keeps the experience smooth.

Common Mistakes

Several things can go wrong even after a correct setup. These are the ones seen most often in real deployments.

Forgetting to set createUpdaterArtifacts to true. The build succeeds but produces no .sig files. The endpoint JSON will be missing valid signatures, and updates will fail verification.

Mismatched version strings. The version in tauri.conf.json must match the version you advertise in the endpoint JSON. If the app says 1.0.0 but the server says 1.0.0-beta.1, the updater may not consider it an update depending on the SemVer ordering. Keep versions consistent.

Private key leaks. Accidentally committing the .key file or printing it in build logs compromises the entire update chain. Anyone with the key can sign malicious updates. Always treat the private key and password as production secrets.

Serving the wrong artifact URL. On macOS, the updater expects a .tar.gz file, not the raw .app bundle. On Linux, it expects the .AppImage directly, not a .tar.gz. If the endpoint points to the wrong file type, the download will succeed but the installation will fail with a cryptic error.

Using a local HTTP server without disabling TLS enforcement. In development, if your endpoint starts with http://, the updater will refuse to connect unless you set dangerousInsecureTransportProtocol: true. This is only safe for local testing.

Summary

Release management for the Tauri updater comes down to three core responsibilities: assigning a clear SemVer version, producing signed update artifacts with every build, and hosting an endpoint that maps those artifacts to platform‑specific download URLs. Each of these steps is mechanical — version bump, set environment variables, run tauri build, upload the output — but the security model is strict. The updater will not function without a valid signature, and there is no escape hatch to bypass it.

A solid release pipeline cements the updater's role: every time you ship a fix or a feature, your users get it automatically, with cryptographic assurance that the update genuinely came from you.