Tauri’s updater is deliberately simple: your app asks an endpoint whether a newer version exists, downloads the returned artifact, and verifies its signature.
The awkward part begins after the build. Someone still has to collect each platform’s artifact and signature, publish them, keep release metadata current, and return the right response for the requesting target and architecture.
I’m building Mateality Releases to handle that delivery layer. Signing and notarization stay in your CI; the service stores the already-signed output and serves Tauri’s updater response.
This walkthrough shows the complete path.
The architecture
The release flow has four parts:
- GitHub Actions builds and signs the desktop app.
- The workflow uploads the artifact and its adjacent
.sigfile. - Mateality publishes that version to a release channel such as
stable. - The installed app checks a dynamic endpoint. It receives JSON when an update is available and HTTP
204when it is already current.
The private signing key never needs to be uploaded to Mateality. It remains in your CI secrets.
Prerequisites
You need:
- a Tauri v2 app that already produces signed updater artifacts;
- a Mateality organization and app;
- a publish-only API key stored as the GitHub Actions secret
MATEALITY_RELEASES_KEY.
The Hobby plan supports one app, so the setup can be tested without committing to a paid plan.
1. Configure the Tauri updater
Enable updater artifacts and point the updater plugin at your app’s endpoint:
{"bundle":{"createUpdaterArtifacts":true},"plugins":{"updater":{"pubkey":"YOUR_TAURI_PUBLIC_KEY","endpoints":["https://YOUR-PUBLIC-SLUG.mateality.app/releases/stable/{{target}}/{{arch}}/{{current_version}}"]}}}
Keep the public key in the app configuration. Keep its corresponding private key in CI.
Tauri replaces {{target}}, {{arch}}, and {{current_version}} at runtime. That lets one endpoint resolve the correct artifact for macOS, Windows, or Linux.
2. Build and sign as usual
Your existing Tauri workflow remains responsible for:
- compiling the app;
- code signing and notarization where required;
- producing the updater artifact;
- producing the Tauri
.sigsignature.
For example, a macOS updater artifact may look like:
MyApp.app.tar.gz
MyApp.app.tar.gz.sig
Keep the signature beside the artifact. The upload action detects that adjacent file automatically.
3. Upload from GitHub Actions
Add the upload step after the build:
- name: Publish desktop update
uses: mateality/releases-upload@v1
with:
api-key: ${{ secrets.MATEALITY_RELEASES_KEY }}
app: my-desktop-app
version: ${{ github.ref_name }}
artifact: src-tauri/target/release/bundle/macos/MyApp.app.tar.gz
target: darwin-aarch64
channel: stable
notes: "Seethechangelogfordetails."
The artifact glob must resolve to exactly one file. For a multi-platform matrix, run one upload step per target:
darwin-aarch64
windows-x86_64
linux-x86_64
The action uploads the artifact directly, computes its SHA-256 digest, creates a draft release, and publishes it to the selected channel.
4. What the installed app receives
When a newer compatible release exists, the endpoint returns the structure Tauri expects:
{"version":"1.4.0","notes":"See the changelog for details.","pub_date":"2026-07-31T12:00:00Z","url":"https://...","signature":"..."}
When there is no update, it returns HTTP 204 No Content.
The installed app still verifies the signature with the public key embedded in its configuration. The hosting layer cannot turn an unsigned artifact into a trusted update.
Common problems
The artifact glob matches nothing—or several files
Use a precise path for each matrix target. The action intentionally refuses an ambiguous glob.
The update downloads but verification fails
Check that the .sig file was generated for that exact artifact and uploaded beside it. Also confirm that the app contains the matching public key.
The endpoint always returns 204
Confirm that:
- the release is published, not left as a draft;
- its channel matches the endpoint, such as
stable; - the uploaded target matches the requesting platform;
- the published version is newer than the installed version.
Why use a dynamic endpoint?
A static latest.json file is workable for a small release process, but it becomes another deployment artifact that must stay synchronized with every binary and signature. A dynamic endpoint resolves releases from the channel, target, architecture, and installed version at request time.
That also leaves room for stable, beta, or internal channels without shipping different updater logic in the app.
If you want to try this flow, start with the Tauri v2 guide or the GitHub Action quickstart.
I’d especially value feedback from Tauri maintainers already shipping on more than one operating system: which part of the release pipeline still causes the most friction for you?