# How the Modly Desktop App Checks for and Applies Updates with the Auto‑Updater

> Learn how the Modly desktop app uses electron-updater to automatically apply patch updates on macOS and Windows. Get insights into the auto-updater process and user prompts.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: how-to-guide
- Published: 2026-08-15

---

**The Modly desktop client uses **electron‑updater** to automatically download patch updates on macOS and Windows, while prompting users manually for major or minor version upgrades.**

The Modly auto-updater balances seamless background updates with user control by distinguishing between patch releases (applied silently) and feature releases (deferred to the user). This article examines the complete implementation in the `lightningpixel/modly` repository, from initialization through renderer notification.

## Initializing the Auto‑Updater in the Main Process

When the Electron main process starts, [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) initializes the updater with a reference to the main window:

```typescript
// electron/main/index.ts – line 108
initAutoUpdater(() => mainWindow);

```

This wiring enables the updater to send IPC messages to the renderer. On macOS, auto-download is disabled entirely because Modly's builds are unsigned, preventing automatic gatekeeper bypasses.

## Configuring the Updater Behavior

The core configuration resides in [`electron/main/updater.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/updater.ts) (lines 19‑23):

```typescript
autoUpdater.logger = createAppLogger();
autoUpdater.autoDownload = false;           // patches download only after confirmation
autoUpdater.autoInstallOnAppQuit = true;    // install when app exits normally
autoUpdater.disableWebInstaller = true;

```

**Key design decisions:**

- **`autoDownload: false`** — Prevents immediate downloads, allowing the app to inspect version semantics first
- **`autoInstallOnAppQuit: true`** — Ensures updates apply without forcing an immediate restart
- **Web installer disabled** — Keeps all update traffic within the native auto-updater channels

## How Modly Checks for Updates

The updater employs a three-trigger strategy defined in [`electron/main/updater.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/updater.ts):

### 1. Startup Check (Lines 59‑62)

```typescript
autoUpdater.checkForUpdates().catch(err => {
  logger.error('Initial update check failed', err);
});

```

Executes immediately when the app launches, ensuring users receive critical patches promptly.

### 2. Periodic Re‑check (Lines 64‑67)

```typescript
setInterval(() => {
  autoUpdater.checkForUpdates().catch(() => {});
}, 2 * 60 * 60 * 1000); // 2 hours

```

Background polling catches updates published while the app remains open for extended sessions.

### 3. Manual Check via IPC (Lines 1437‑1443)

The renderer can force an immediate check through [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts):

```typescript
ipcMain.handle('updater:check', async () => {
  const result = await autoUpdater.checkForUpdates();
  return { success: true, versionInfo: result?.updateInfo };
});

```

## Differentiating Patch vs. Major/Minor Updates

When `update-available` fires, [`electron/main/updater.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/updater.ts) (lines 24‑40) performs semantic version analysis:

```typescript
autoUpdater.on('update-available', (info) => {
  const current = app.getVersion();      // e.g., "1.2.3"
  const incoming = info.version;          // e.g., "1.2.4" or "1.3.0"
  
  const currentParts = current.split('.').map(Number);
  const incomingParts = incoming.split('.').map(Number);
  
  const isPatch = (
    currentParts[0] === incomingParts[0] &&  // same major
    currentParts[1] === incomingParts[1]      // same minor
  );
  
  if (isPatch) {
    autoUpdater.downloadUpdate();  // silent background download
  } else {
    mainWindow?.webContents.send('updater:major-minor-available', {
      version: incoming,
      currentVersion: current
    });
  }
});

```

**Patch updates** (1.2.3 → 1.2.4) download automatically. **Major/minor updates** (1.2.3 → 1.3.0) surface in the UI for explicit user action.

## Downloading and Applying Patch Updates

Once a patch downloads, the `update-downloaded` event triggers (lines 42‑49):

```typescript
autoUpdater.on('update-downloaded', (info) => {
  mainWindow?.webContents.send('updater:applying', {
    version: info.version
  });
  
  setTimeout(() => {
    autoUpdater.quitAndInstall(true, true);
    // first true = silent, second true = force run after update
  }, 800);  // brief delay for UI transition
});

```

The 800ms timeout allows the renderer to render an "Applying update..." panel before the app terminates.

## Renderer‑Side API for Update Control

The preload script in [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) (lines 63‑76) exposes a typed `updater` object to the React frontend:

```typescript
updater: {
  check: () => ipcRenderer.invoke('updater:check'),
  quitAndInstall: () => ipcRenderer.invoke('updater:quitAndInstall'),
  
  onApplying: (callback) => 
    ipcRenderer.on('updater:applying', (_e, data) => callback(data)),
  offApplying: () => 
    ipcRenderer.removeAllListeners('updater:applying'),
  
  onMajorMinorAvailable: (callback) => 
    ipcRenderer.on('updater:major-minor-available', (_e, data) => callback(data)),
  offMajorMinorAvailable: () => 
    ipcRenderer.removeAllListeners('updater:major-minor-available')
}

```

### Triggering a Manual Check from React

```typescript
import { api } from '@/electron/preload';

const checkForUpdates = async () => {
  const result = await api.updater.check();
  if (result.success) {
    console.log('Update check initiated');
  }
};

```

### Displaying the Applying State

```typescript
useEffect(() => {
  api.updater.onApplying(({ version }) => {
    setApplyingVersion(version);
    setShowApplyingPanel(true);
  });
  
  return () => api.updater.offApplying();
}, []);

```

### Handling Major/Minor Version Notifications

```typescript
useEffect(() => {
  api.updater.onMajorMinorAvailable(({ version }) => {
    openDownloadDialog(version);  // user-controlled download flow
  });
  
  return () => api.updater.offMajorMinorAvailable();
}, []);

```

## Platform-Specific Considerations

| Platform | Behavior |
|----------|----------|
| **macOS** | Auto-download disabled (`autoDownload: false`) due to unsigned builds; manual download required for all updates |
| **Windows** | Full auto-updater support; patches download and install automatically |
| **Linux** | Not configured in current implementation; typically distributed via package managers |

## Summary

- **Initialization** occurs in [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) via `initAutoUpdater()`, binding the updater to the main window
- **Configuration** in [`electron/main/updater.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/updater.ts) disables auto-download and enables quit-time installation
- **Three triggers** check for updates: startup, 2‑hour interval, and manual IPC invocation
- **Semantic versioning** differentiates patches (auto-downloaded) from major/minor releases (renderer notification)
- **Patch application** uses `quitAndInstall(true, true)` with an 800ms UI grace period
- **Renderer API** in [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) provides `check()`, `onApplying()`, and `onMajorMinorAvailable()` for complete UI control

## Frequently Asked Questions

### How often does Modly check for updates automatically?

The app checks immediately on startup and then every 2 hours via `setInterval` in [`electron/main/updater.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/updater.ts) (lines 64‑67). Users can also trigger manual checks through the `updater:check` IPC handler.

### Why doesn't Modly auto-download updates on macOS?

macOS builds are unsigned, so `autoDownload` remains `false` to prevent Gatekeeper warnings and security prompts. Users must manually download and replace the application for macOS updates.

### What happens when a major version update is available?

The main process sends `updater:major-minor-available` to the renderer with version details. The UI typically displays a dialog directing users to the GitHub releases page rather than applying the update automatically.

### Can users cancel an update that's already downloading?

Once `downloadUpdate()` begins for a patch, cancellation isn't exposed through the current API. However, major/minor updates require explicit user action before any download starts, providing natural cancellation.

### Where is the update server configured?

The `electron-updater` module reads update metadata from [`latest.yml`](https://github.com/lightningpixel/modly/blob/main/latest.yml) (Windows) or [`latest-mac.yml`](https://github.com/lightningpixel/modly/blob/main/latest-mac.yml) (macOS) hosted alongside the release artifacts. The feed URL derives from the `publish` configuration in [`package.json`](https://github.com/lightningpixel/modly/blob/main/package.json), not hardcoded in the source files examined.