Publish a Tauri app for Windows

This guide adds Windows (windows-x86_64) to a Tauri 2 app that publishes to Mocco. Set up the app, the updater key, tauri.conf.json and the CI key first, as in Publish a Tauri app; everything there applies to Windows too.

Pick a bundle

Tauri builds two kinds of Windows installer. Use NSIS unless you need MSI:

Bundletauri build --bundlesFiles in src-tauri/target/release/bundle/
NSIS (.exe)nsisnsis/App_0.2.0_x64-setup.exe and nsis/App_0.2.0_x64-setup.exe.sig
MSI (.msi)msimsi/App_0.2.0_x64_en-US.msi and msi/App_0.2.0_x64_en-US.msi.sig

NSIS installs per user without asking for an administrator, and its installer updates in place. MSI suits machines managed by group policy. With createUpdaterArtifacts: true, Tauri signs the installer itself for the updater, so the same file is both what people download and what the app installs. (With "v1Compatible", the update bundle is a separate .nsis.zip or .msi.zip with its own .sig; upload that as the update bundle instead.)

A release has one installer and one update bundle per platform, so publish NSIS or MSI, not both. Installed apps update with the kind of installer they were installed with: if you switch from MSI to NSIS, existing MSI installs get the NSIS installer.

Sign the installer first

Authenticode signing is yours, and it happens before the upload. Without it, Windows SmartScreen warns people who download the installer. Configure it in tauri.conf.json (bundle.windows.certificateThumbprint, or bundle.windows.signCommand for a cloud signing service), so Tauri signs the .exe or .msi during the build and then writes the updater .sig over the signed file.

If you sign the file after the build instead, its bytes change and the .sig no longer matches: sign it again for the updater (tauri signer sign) before the upload, or Mocco marks the release Failed with The signature doesn't match the file.

Mocco never holds your certificate or your updater private key.

The release step

On a Windows runner, after the build:

- name: Publish to Mocco
  uses: fi-workers/mocco/actions/desktop-publish@<commit sha>
  with:
    app-id: <app id>
    version: ${{ steps.version.outputs.version }}
    notes-file: RELEASE_NOTES.md
    artifacts: |
      windows-x86_64 installer src-tauri/target/release/bundle/nsis/App_${{ steps.version.outputs.version }}_x64-setup.exe
      windows-x86_64 updater   src-tauri/target/release/bundle/nsis/App_${{ steps.version.outputs.version }}_x64-setup.exe
    channel: stable
  env:
    MOCCO_API_KEY: ${{ secrets.MOCCO_API_KEY }}

The same file is listed twice, once per kind; the .sig next to it is read for the updater line. For MSI, use the two msi/…_x64_en-US.msi lines instead.

Mocco serves the update under windows-x86_64 and under the key newer updater plugins look up first: windows-x86_64-nsis, or windows-x86_64-msi when the update bundle's name ends in .msi or .msi.zip. Nothing changes in tauri.conf.json: the endpoint's {{target}}/{{arch}} is already windows/x86_64.

Build every platform, publish one release

Build each platform on its own runner, then publish all the files as one release from a single job, so the version appears everywhere at once and a failed build publishes nothing.

name: Release
on:
  push:
    tags: ['v*']

jobs:
  build:
    strategy:
      matrix:
        include:
          - runner: macos-latest
            platform: darwin-aarch64
            bundles: app,dmg
          - runner: windows-latest
            platform: windows-x86_64
            bundles: nsis
          - runner: ubuntu-22.04
            platform: linux-x86_64
            bundles: appimage
    runs-on: ${{ matrix.runner }}
    env:
      TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
      TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
    steps:
      - uses: actions/checkout@<commit sha>
      # Install Node, Rust and (on Linux) the system packages Tauri needs here.
      - run: npm ci
      - run: npm run tauri build -- --bundles ${{ matrix.bundles }}
      - uses: actions/upload-artifact@<commit sha>
        with:
          name: ${{ matrix.platform }}
          path: |
            src-tauri/target/release/bundle/dmg/*.dmg
            src-tauri/target/release/bundle/macos/*.app.tar.gz*
            src-tauri/target/release/bundle/nsis/*-setup.exe*
            src-tauri/target/release/bundle/appimage/*.AppImage*

  publish:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@<commit sha>
        with:
          path: release
      - id: version
        run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
      - name: Publish to Mocco
        uses: fi-workers/mocco/actions/desktop-publish@<commit sha>
        with:
          app-id: <app id>
          version: ${{ steps.version.outputs.version }}
          artifacts: |
            darwin-aarch64 installer release/darwin-aarch64/dmg/App_${{ steps.version.outputs.version }}_aarch64.dmg
            darwin-aarch64 updater   release/darwin-aarch64/macos/App.app.tar.gz
            windows-x86_64 installer release/windows-x86_64/App_${{ steps.version.outputs.version }}_x64-setup.exe
            windows-x86_64 updater   release/windows-x86_64/App_${{ steps.version.outputs.version }}_x64-setup.exe
            linux-x86_64   installer release/linux-x86_64/App_${{ steps.version.outputs.version }}_amd64.AppImage
            linux-x86_64   updater   release/linux-x86_64/App_${{ steps.version.outputs.version }}_amd64.AppImage
          channel: stable
        env:
          MOCCO_API_KEY: ${{ secrets.MOCCO_API_KEY }}

upload-artifact keeps the folders below the paths' common root, so check the downloaded layout once (ls -R release) and adjust the paths. Pin every action to a commit. The publish job needs no Rust, no signing key and no Windows runner: it only reads the files and their .sig.

Next