# 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](https://www.mocco.club/docs/en/articles/2pegvw-publish-tauri); everything there applies to Windows too.

## Pick a bundle

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

| Bundle       | `tauri build --bundles` | Files in `src-tauri/target/release/bundle/` |
| ------------ | ----------------------- | ------------------------------------------- |
| NSIS (`.exe`) | `nsis`                 | `nsis/App_0.2.0_x64-setup.exe` and `nsis/App_0.2.0_x64-setup.exe.sig` |
| MSI (`.msi`) | `msi`                   | `msi/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:

```yaml
- 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.

```yaml
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

- [Publish a Tauri app for Linux](https://www.mocco.club/docs/en/articles/r79gy9-publish-tauri-linux): AppImage, deb and rpm.
- [Publish with curl](https://www.mocco.club/docs/en/articles/by2fx3-publish-with-curl): the same upload from any CI. Use the content type `application/vnd.microsoft.portable-executable` for an `.exe` and `application/x-msi` for an `.msi`.
