Your users installed version 1.0.0. This page gets 1.0.1 onto their machines without asking them to do anything.
How it works
Every installed copy periodically fetches a small file called latest.yml from wherever you publish. If the version in that file is newer than the version running, the app downloads the installer named inside it and offers to restart.
Two decisions: where that file lives, and making sure the name inside it matches the file you uploaded.
Choose a host
GitHub Releases is free and needs nothing beyond the repository you already have. Your releases are public, which is usually fine and sometimes not.
Any S3 compatible bucket (Cloudflare R2, Backblaze B2, AWS S3, MinIO) keeps releases private and off GitHub. It needs a bucket and a public URL.
GitHub is the default. For a bucket, put this in .env:
PUBLISH_TARGET=generic
PUBLISH_PUBLIC_URL=https://releases.yourdomain.com
PUBLISH_BUCKET=my-app-releases
PUBLISH_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
PUBLISH_REGION=auto
PUBLISH_ACCESS_KEY_ID=...
PUBLISH_SECRET_ACCESS_KEY=...The URL must be https
The build refuses to bake an http:// update URL, on purpose. Every installed copy of your app downloads and runs whatever that URL serves. Someone who can intercept plain HTTP can hand your users a program of their choosing, on every machine you have shipped to. That is the single worst thing you can misconfigure here.
Ship an update
- Make your changes and commit them.
- Raise the version in
package.json:1.0.0becomes1.0.1. - Build:
npm run build:win. - Publish:
npm run publish.
npm run publish uploads the installer, the blockmap, and latest.yml. Add --dry-run first to see exactly what it will upload without uploading it.
Installed apps pick up the new version on their next check. The blockmap means most users download only the parts that changed rather than the whole installer again.
Verify the first one, by hand
Do this once per project. It takes two minutes and it catches the failure that is otherwise invisible.
1. Compare the two names. Open dist/latest.yml:
version: 1.0.1
path: electron-starter-1.0.1-x64-setup.exeThe path must match the installer filename in dist/ exactly.
Why this check exists
If your app's display name contains a space, some build configurations write a filename into latest.yml that does not match the file on disk. The result is the worst kind of failure: the update notification appears, the user clicks update, and the download 404s. Every time. Nobody reports it because from the outside it just looks like the update is slow.
This template was shipping exactly that bug until it was found by driving a packaged build against a real feed. It is fixed, and the filenames now come from the npm package name, which cannot contain spaces. Check anyway on your first release; it costs ten seconds.
2. Fetch the feed you published.
curl https://releases.yourdomain.com/releases/latest.yml3. Download the file it names. Copy the path value into a URL and fetch it. If that returns anything other than a file, your users' updates are broken, and now you know before they do.
4. Install the old version, then run it with the new one published, and watch the update arrive. It is the only test that proves the whole chain.
Two things people get wrong
Auto-update does nothing in development. It only runs in a packaged build. Seeing "no updates" during npm run dev is correct behaviour, not a broken setup.
Never republish a version number. Users who already have 1.0.1 will not fetch it again, and users who do not have it may get a mismatched hash. Ship 1.0.2 instead.
I want to publish this Electron app's updates to a Cloudflare R2 bucket instead of GitHub Releases. Walk me through the .env values in .env.example under 'Generic feed', run npm run publish -- --dry-run, and then check that the filename in dist/latest.yml matches the artifact that would be uploaded.
Next: Where to Go Next.