Electron

For Electron apps built with electron-builder. Create the app with type Electron. One route serves both electron-updater (generic provider) and Electron’s built-in autoUpdater on macOS:

  • GET https://releaseflow.net/{username}/{slug}/update/{platform}/{version}: update check, JSON or 204.
  • GET https://releaseflow.net/{username}/{slug}/update/{platform}/{version}/{fileName}: metadata and installer files of the latest release, redirected to https://releaseflow.net/{username}/{slug}/updates/download/{version}/{fileName}.

{platform} is darwin, windows or linux (also mac, win32, appimage and similar aliases). {version} is the running app version and must be SemVer, otherwise the route returns 400.

Publish builds to GitHub

ReleaseFlow reads the GitHub release, so publish there as usual. Keep the files electron-builder generates: the installers, the latest.yml / latest-mac.yml / latest-linux.yml metadata and the .blockmaps.

# electron-builder.yml: publish installers and latest*.yml to GitHub releases
publish:
  provider: github
mac:
  target: [dmg, zip] # the .zip is what macOS updates install

Updates use the .zip on macOS, the .exe on Windows and the .AppImage on Linux. Add ?arch=arm64, ?arch=x64 or ?arch=universal if you ship separate builds per architecture.

electron-updater (generic provider)

Point the generic provider at the update route with the current version. electron-updater then requests latest-mac.yml (or latest.yml, latest-linux.yml) and the installer relative to that URL, which ReleaseFlow resolves against the latest enabled release.

main process
import { app } from "electron";
import { autoUpdater } from "electron-updater";

const platform = { darwin: "darwin", win32: "windows", linux: "linux" }[
  process.platform as "darwin" | "win32" | "linux"
];

autoUpdater.setFeedURL({
  provider: "generic",
  url: `https://releaseflow.net/{username}/{slug}/update/${platform}/${app.getVersion()}`,
});
autoUpdater.checkForUpdatesAndNotify();

Query parameters on the feed URL are kept on every request electron-updater makes, so a beta build only needs ?channel=beta:

autoUpdater.setFeedURL({
  provider: "generic",
  url: `https://releaseflow.net/{username}/{slug}/update/${platform}/${app.getVersion()}?channel=beta`,
});

For a beta or alpha release ReleaseFlow serves beta-mac.yml / alpha-mac.yml when the release has one, and falls back to its latest-mac.yml.

Integrity and code signing

electron-updater checks every download against the sha512 in the latest*.yml that electron-builder published with the release; ReleaseFlow serves those files unchanged and stores the hashes when it syncs the release, so keep the yml files in the GitHub release. The hash only proves the file matches the metadata. Code-sign your builds (Developer ID and notarization on macOS, Authenticode on Windows): electron-updater verifies the signature of the new build before installing on macOS and Windows, and that is what protects users if a feed or release is ever tampered with.

Update JSON route

Without a file name the route answers the update check itself. Electron’s built-in autoUpdater on macOS (Squirrel.Mac) uses exactly this shape:

main process
import { app, autoUpdater } from "electron";

// macOS (Squirrel.Mac): 200 + JSON when a newer .zip exists, 204 otherwise.
autoUpdater.setFeedURL({
  url: `https://releaseflow.net/{username}/{slug}/update/darwin/${app.getVersion()}`,
});
autoUpdater.checkForUpdates();

When a newer compatible release exists the response is 200:

response
{
  "name": "1.2.0",
  "notes": "Release notes from the GitHub release",
  "pub_date": "2026-10-21T09:00:00.000Z",
  "url": "https://releaseflow.net/{username}/{slug}/updates/download/1.2.0/MyApp-1.2.0-mac.zip"
}

When the app is already on the newest version, or nothing matches the platform and architecture, the response is 204 with no body.

Debugging

Every response carries x-releaseflow-debug-reason (update_available, no_newer_release, no_release, missing_asset, unknown_platform, ...) and x-releaseflow-resolved-channel. Add ?debug=1 for a JSON body with the resolved release and asset, and ?sync=1 to sync from GitHub before the lookup (owner only: signed in, or your API key in x-api-key; ignored for everyone else, at most one sync per app every 5 minutes):

curl -si "https://releaseflow.net/{username}/{slug}/update/darwin/1.0.0?debug=1"

More causes and fixes: /help.

Licensing

Gate the app with the license endpoints over plain fetch; see License keys.