GenerateSaaS

Updates and downloads

Background auto-updates via electron-updater, the per-OS update feed, and the /download page plus the always-latest installer links behind it.

Packaged builds update themselves through electron-updater, and your users get the installers from the public /download page. Both read the feed published by the release workflow.

Auto-updates

On launch the app checks the feed in the background, downloads a newer version, and installs it on the next quit. Integrity comes from the installer's code signature and per-file .blockmap, so there is no separate signing key to manage.

  • Feed: <config.desktop.autoUpdate.url>/latest-mac.yml on macOS (latest.yml on Windows, latest-linux.yml on Linux), baked into the packaged app by electron-builder.
  • Check: Settings has "Check for updates" and an install action. A finished background download also raises an in-app "Restart to update" toast; installing quits and relaunches into the new version, and dismissing it leaves the update to install on the next quit.
  • Dev builds report updates as unavailable; apps/desktop/dev-app-update.yml points electron-updater at a feed under electron-vite dev to test the flow.
PlatformUpdater artifactCoverage
macOS.zipfull auto-update (signed builds only)
Windowsnsis setupfull auto-update
LinuxAppImageAppImage only - .deb users update through their package manager

Unsigned macOS builds cannot auto-update. Ship a signed macOS build (see Releasing) before relying on updates there. With AI on, the runtime needs no separate update: the app forks it from its own binary, so replacing the bundle replaces it too.

The download page

Your users get the app at /download, a public page your site already ships and links from the footer. It reads the update feed and lists one card per platform with its published version and installer link.

  • Before your first release the feed does not exist, so the page says "No release yet" rather than offering a link that 404s - what a freshly generated project shows.
  • After a release, each platform whose feed resolves gets a card. A platform you did not build is not offered.
  • With the app off (config.desktop.enabled: false) the route 404s and the footer link is hidden.

Every release publishes a stable, always-latest installer per platform under config.desktop.autoUpdate.url, named from config.desktop.protocol:

PlatformURL
Windows<autoUpdate.url>/<protocol>-Setup.exe
macOS<autoUpdate.url>/<protocol>.dmg
Linux<autoUpdate.url>/<protocol>.AppImage

To put a download button elsewhere (a navbar, a landing hero), @repo/utils/desktop-download builds those URLs:

FunctionReturns
fetchDesktopReleases()the published releases ({ platform, version, url }), empty when nothing is released - what the download page renders
desktopDownloadUrls()the per-OS map ({ mac, windows, linux }) of installer URLs, without checking the feed
desktopDownloadUrlFor(userAgent)the single installer URL for the visitor's OS (null when unknown)

desktopDownloadUrls() and desktopDownloadUrlFor() skip the feed, so their URLs 404 until the first release - use fetchDesktopReleases() where a dead link would be visible. Call it from the server: the feed is a public bucket that sends no CORS headers. OS detection needs navigator.userAgent, so read it client-side with a fallback that lists every platform.

The app moved to apps/desktop

Since 3.4.3 the desktop app lives at apps/desktop (it was apps/electron). generatesaas update never deletes your files, so a project generated earlier keeps the old directory beside the freshly staged one, and update says so once.

Move any local changes from apps/electron into apps/desktop.
Remove apps/electron, then run pnpm install so the lockfile drops the old workspace path.
Update anything of yours that names the old path - CI workflows, scripts, editor configs.

Migrating from the Tauri shell

The desktop app moved from Tauri to Electron. generatesaas update never deletes your files, so a project scaffolded before the move keeps apps/tauri beside the updated apps/desktop, and the update prints a notice when it sees both.

Build and run the new shell (pnpm --filter desktop dev, then a packaged build) and confirm sign-in, updates, and any AI features work.
Remove apps/tauri - the frozen shell is no longer built, released, or referenced.

Your config.desktop identity carries over unchanged, so installers and the update feed keep their names.

On this page