BuildBot

Web to Desktop

App data & updates

Lesson 4 of 5

What you'll learn

  • Locate per-user app storage with app.getPath("userData")
  • Persist settings safely: defaults merge, atomic writes, or electron-store
  • Follow the auto-update flow from feed check to quitAndInstall

A desktop app can't lean on a server database for its own preferences — it needs a home on disk. Electron gives every app one: app.getPath("userData") returns a per-user, per-app directory (~/Library/Application Support/YourApp on macOS, %APPDATA%\YourApp on Windows, ~/.config/YourApp on Linux). Settings, caches, and local documents all belong under it.

const { app } = require("electron");
const path = require("node:path");

const settingsPath = path.join(app.getPath("userData"), "settings.json");

The settings pattern

The classic pattern is a single JSON file with three rules. Merge over defaults when loading, so a missing file or a setting added in v2 both just work. Write atomically — serialize to a temp file, then rename over the real one — so a crash mid-write can't leave half a JSON file. Read and write in main, exposing settings:get/settings:set over IPC, so there's one owner and no write races between windows.

The electron-store package is that exact pattern productized: same JSON-in-userData layout, plus atomic writes, dot-path accessors, JSON-schema validation, and migrations. Hand-roll the file for two keys; reach for electron-store once settings grow real structure.

Never write inside the app itself

The installed app bundle is read-only in practice — and on macOS, modifying it breaks the code signature. Anything the app writes at runtime goes in userData (or temp/logs, also available via app.getPath).

The auto-update loop

Auto-update, at a high level, is four steps. The de-facto tool is electron-updater (from the electron-builder project), which reads a feed file (latest.yml) published next to your installers on GitHub Releases, S3, or any static host:

  1. On launch, autoUpdater.checkForUpdatesAndNotify() fetches the feed and compares versions.
  2. If newer, the update downloads in the background while the user works.
  3. update-downloaded fires; you nudge the user ("Restart to update") or stay silent.
  4. autoUpdater.quitAndInstall() swaps the app and relaunches it.
const { autoUpdater } = require("electron-updater");
autoUpdater.checkForUpdatesAndNotify();
autoUpdater.on("update-downloaded", () => {
  // e.g. show a "Restart now?" dialog, then:
  autoUpdater.quitAndInstall();
});

One platform rule to know now (it shapes the next lesson): macOS refuses to install updates into an unsigned app — code signing isn't optional once you auto-update.

The challenge is a JS model of the settings store: load-merge-save round-trips against an in-memory "disk," with the atomic temp-then-rename dance spelled out.

A settings store with an in-memory disk (JS model)

Run it. First load falls back to defaults, save writes temp-then-rename, and the second load round-trips the change.

Loading editor…
Knowledge check

Why write settings to a temp file and then rename it over the real one?

Next: turning the project into something you can hand to users — packaging, signing, notarization, and publishing releases.

Saved on this device. Sign in to sync your progress everywhere.