releaseflow
PricingDocsLog in

Hazel / Nuts

Docs

  • Overview
  • Authentication
  • Quickstart
  • Sparkle (macOS)
  • Electron
  • License keys
  • Channels
  • From S3 feed
  • From Hazel / Nuts
  • API reference

On this page

  • How the move works
  • 1. Create the app
  • 2. Map the URLs
  • 3. Verify
  • 4. Switch the app
  • 5. Old installs
  • 6. Roll out
  • Limits

For Electron apps that check for updates against a Hazel or Nuts deployment, or against update.electronjs.org. All three read your GitHub Releases, and so does ReleaseFlow, so you keep publishing exactly as you do now. The move is a feed URL change in the app plus a transition period for builds already installed.

How the move works

  • Publishing does not change: the same GitHub release is served by the old server and by ReleaseFlow at the same time.
  • ReleaseFlow’s update route answers Electron’s built-in autoUpdater on macOS (Squirrel.Mac) with the same shape Hazel uses: 200 + JSON when a newer version exists, 204 when not.
  • It also serves electron-updater’s generic provider: latest.yml, latest-mac.yml, latest-linux.yml and the files they list, which is how Windows (NSIS) and Linux (AppImage) update.
  • Assets are streamed from GitHub through https://releaseflow.net/{username}/{slug}/updates/download/{version}/{fileName}, so private repositories work without a token in the app or a public mirror.

1. Create the Electron app

  1. Open /apps/new, choose Electron and select the GitHub repository the old server reads (Hazel’s ACCOUNT/REPOSITORY, Nuts’ GITHUB_REPO, or OWNER/REPO in the update.electronjs.org URL).
  2. Press Sync Releases. Tags need to be SemVer; versions containing beta or rc land in the beta channel, alpha in alpha, the rest in stable. Enable beta or alpha in the app settings if you serve them.
  3. Check the asset names ReleaseFlow will pick. Updates use the .zip on macOS, the .exe on Windows and the .AppImage on Linux. The architecture comes from the file name: arm64/aarch64, x64/x86_64/amd64, universal. A name with no architecture, like electron-builder’s MyApp-1.3.0-mac.zip, counts as compatible with x64 only; send ?arch= when you ship separate Intel and Apple Silicon builds.

2. Map the old URLs

WhatBeforeReleaseFlow
Update check (macOS)Hazel /update/darwin/:version · Nuts /update/osx/:version · electronjs.org /OWNER/REPO/darwin-arm64/:version/{username}/{slug}/update/darwin/{version}?arch=arm64
Pre-release channelNuts /update/channel/beta/:platform/:version · Hazel PRE=1 deployment…/update/{platform}/{version}?channel=beta
Download, detect OSHazel /download · Nuts /download/latest/{username}/{slug}/download
Download per OSHazel /download/:platform · Nuts /download/:platform/{username}/{slug}/download/{platform}?arch=arm64
Download a versionNuts /download/version/:tag/:platform/{username}/{slug}/releases/download/{version}/{fileName}
Release notesNuts /notes/:version/{username}/{slug}/releases (JSON, notes per release)
Download pageHazel / overview page/{username}/{slug}

ReleaseFlow URLs live on https://releaseflow.net. The platform segment accepts darwin, mac, macos, osx, win32, windows, win and linux. Combined values such as Nuts’ darwin_x64 or update.electronjs.org’s darwin-arm64 return 400 unknown_platform: send the architecture as ?arch= instead. The version must be SemVer, or the route returns 400 invalid_version.

3. Verify before switching

Ask the route what an old build would get. ?debug=1 returns JSON with the reason, the resolved release and the asset; ?sync=1 syncs from GitHub first, but only for the app owner (signed in, or with the owner’s API key in x-api-key); for anyone else it is ignored.

# Pretend to be an old build; expect "reason": "update_available"
# (?sync=1 is honoured only with the app owner's API key)
curl -s "https://releaseflow.net/{username}/{slug}/update/darwin/1.0.0?arch=arm64&debug=1&sync=1" \
  -H "x-api-key: $RELEASEFLOW_API_KEY"

# Squirrel.Mac response shape (no debug)
curl -si "https://releaseflow.net/{username}/{slug}/update/darwin/1.0.0?arch=arm64"

The normal response is what Squirrel.Mac reads. url points at the ReleaseFlow download proxy:

{
  "name": "1.3.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.3.0/MyApp-1.3.0-arm64-mac.zip"
}

no_newer_release means the version you sent is already the latest; missing_asset means the release has no .zip/.exe/.AppImage for that platform and architecture. More reasons: /help.

4. Switch the feed URL in the app

From Hazel (built-in autoUpdater)

const { app, autoUpdater } = require("electron");

const server = "https://your-hazel.vercel.app";
autoUpdater.setFeedURL({
  url: `${server}/update/${process.platform}/${app.getVersion()}`,
});

becomes:

const { app, autoUpdater } = require("electron");

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

Without ?arch= the route picks the first macOS .zip of the release, which is wrong when you ship separate Intel and Apple Silicon builds.

From Nuts

const platform = os.platform() + "_" + os.arch(); // "darwin_x64"
autoUpdater.setFeedURL(
  "https://download.myapp.com/update/" + platform + "/" + app.getVersion(),
);

Use the same code as for Hazel above: process.platform for the platform segment and process.arch for ?arch=. Replace Nuts’ /update/channel/beta/… with ?channel=beta.

From update.electronjs.org

// update.electronjs.org
require("update-electron-app")();
// or
autoUpdater.setFeedURL(
  `https://update.electronjs.org/OWNER/REPO/${process.platform}-${process.arch}/${app.getVersion()}`,
);

update-electron-app builds the URL as {host}/{repo}/{platform}-{arch}/{version}, which does not match the ReleaseFlow route even with a custom host. Remove it and call autoUpdater.setFeedURL as in the Hazel example, plus your own checkForUpdates() interval and restart prompt.

Windows and Linux: electron-updater

Electron’s built-in autoUpdater on Windows is Squirrel.Windows, which needs a RELEASES file and .nupkg packages; ReleaseFlow does not document that format. For Windows (NSIS) and Linux (AppImage), use electron-updater with the generic provider. It works on macOS too, so one setup can cover all three:

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

// darwin, win32 and linux are all accepted platform segments.
autoUpdater.setFeedURL({
  provider: "generic",
  url: `https://releaseflow.net/{username}/{slug}/update/${process.platform}/${app.getVersion()}`,
});
autoUpdater.checkForUpdatesAndNotify();

Keep publishing with electron-builder to GitHub so the latest*.yml files and blockmaps are in each release:

# electron-builder.yml: keep publishing to GitHub Releases
publish:
  provider: github
mac:
  target: [dmg, zip] # the .zip is what macOS updates install
win:
  target: nsis       # latest.yml + .exe for electron-updater

5. Keep old installs updating

Installed builds still ask the old server. Keep it running and keep publishing to the same GitHub repository; it serves each new release exactly as before, including the release that contains the ReleaseFlow feed URL. Users who install that release move over on their next check.

  • Hazel / Nuts: leave the deployment (and its GitHub token, for private repositories) in place until your analytics show no meaningful traffic from old versions, then shut it down. Same-origin download links on your website can move to https://releaseflow.net/{username}/{slug}/download right away.
  • update.electronjs.org: it keeps serving your public repository’s latest non-prerelease release. Nothing to keep running on your side.
  • Squirrel.Windows installs (Hazel and Nuts /update/win32/:version/RELEASES, Electron Forge’s Squirrel maker): they can only update from a server that serves RELEASES. Keep the old server for them and ship NSIS installers with electron-updater for new installs.

Do not move the old domain to ReleaseFlow with a path rewrite unless you test it: platform values like darwin_x64 and darwin-arm64 must become ?arch=, and Windows Squirrel requests will not resolve.

6. Roll out

  1. Build the app with the new feed URL and a version above the current release, and publish it to GitHub. The old server and ReleaseFlow both see it.
  2. Install the previous release on one machine per platform, let it update through the old server, then confirm the updated app checks ReleaseFlow (step 3, with its version in the URL).
  3. Publish the next release and confirm that machine updates from ReleaseFlow.
  4. To stop a bad release, turn its switch off on the app page. It drops out of the update route and its update download returns 403; the old server still serves it until you also mark it as a draft or delete it on GitHub.
  5. Point the download buttons on your site at https://releaseflow.net/{username}/{slug}/download/{platform} or at the hosted page https://releaseflow.net/{username}/{slug}.

Code signing does not change. Keep signing with the same Developer ID: Squirrel.Mac only installs an update that satisfies the running app’s code-signing requirement, wherever it was downloaded from.

Limits

  • No Squirrel.Windows RELEASES/.nupkg or MSIX update format is documented; use NSIS with electron-updater on Windows.
  • Only GitHub Releases are read, like the servers you are leaving.
  • The macOS update needs a .zip in the release; a .dmg alone is only used for downloads.

Sources

  • vercel/hazel README
  • GitbookIO/nuts: macOS updates
  • GitbookIO/nuts: Windows updates
  • update.electronjs.org README
  • update-electron-app README
  • Electron autoUpdater

Next: move from a hand-rolled S3 feed. Feed reference and troubleshooting: /help.

← From S3 feed
releaseflow
PrivacyTermsDocsChangelogSupport