# How Modly's Built‑in Sync Mechanism Works for Extensions in User Data Directories

> Discover how Modly synchronizes built-in extensions by copying them to your user data directory. Learn about its version-file comparison for efficient updates.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: internals
- Published: 2026-08-20

---

**Modly synchronizes built‑in extensions by copying them from the application bundle's `resources/extensions` folder to the user's data directory on startup, using version‑file comparison to detect updates and perform atomic replacements.**

The `lightningpixel/modly` repository implements a robust extension synchronization system that ensures users always have the latest built‑in extensions while preserving their custom installations. This mechanism runs automatically during the Electron main process initialization, handling path resolution, version detection, and safe file operations.

## Where Built‑in Extensions Are Stored

Modly bundles its default extensions inside the Electron application package. The source location depends on whether the app is running in development or production:

- **Production**: `process.resourcesPath/extensions` — the standard Electron resources directory within the compiled app bundle
- **Development**: Typically resolved relative to the project root for local testing

The target directory is always within the user's personal data space: `app.getPath('userData')/extensions`. On most platforms this resolves to:
- macOS: `~/Library/Application Support/Modly/extensions`
- Windows: `%APPDATA%/Modly/extensions`
- Linux: `~/.config/Modly/extensions`

## The Core Sync Algorithm in [`builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/builtin-sync.ts)

The synchronization logic lives in [`electron/main/builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/builtin-sync.ts). This module exports a single async function `syncBuiltinExtensions()` that orchestrates the entire process.

**Version detection strategy**: The system uses a simple [`builtin-version.txt`](https://github.com/lightningpixel/modly/blob/main/builtin-version.txt) file as its source of truth. This file contains a version string or checksum generated at build time. The sync routine compares the version in the source directory against the version already present in the user's extensions folder.

```typescript
// electron/main/builtin-sync.ts (architecture overview)
import { app } from 'electron';
import { promises as fs } from 'fs';
import path from 'path';
import { logger } from './logger';

export async function syncBuiltinExtensions(): Promise<void> {
  const userData = app.getPath('userData');
  const targetDir = path.join(userData, 'extensions');
  const sourceDir = path.join(process.resourcesPath, 'extensions');
  const versionFile = 'builtin-version.txt';

  // Read versions from both locations (empty string if missing)
  const [srcVersion, tgtVersion] = await Promise.all([
    fs.readFile(path.join(sourceDir, versionFile), 'utf8').catch(() => ''),
    fs.readFile(path.join(targetDir, versionFile), 'utf8').catch(() => ''),
  ]);

  // Trigger full replacement only when versions differ
  if (srcVersion !== tgtVersion) {
    logger.info(`Updating built-in extensions: ${tgtVersion} → ${srcVersion}`);
    await replaceExtensions(sourceDir, targetDir);
  }
}

```

This approach avoids unnecessary filesystem operations when the app restarts with no extension updates.

## File Copying with Recursive Directory Traversal

When a version mismatch is detected, the sync mechanism performs an atomic‑style replacement:

1. **Remove** the existing target directory entirely
2. **Recreate** it with fresh contents from the source
3. **Preserve** nested directory structures and file permissions

The `copyDirectory` helper implements depth‑first recursion using Node.js `fs.promises` APIs:

```typescript
// Recursive copy implementation from builtin-sync.ts
async function copyDirectory(src: string, dst: string): Promise<void> {
  const entries = await fs.readdir(src, { withFileTypes: true });
  await fs.mkdir(dst, { recursive: true });

  for (const entry of entries) {
    const srcPath = path.join(src, entry.name);
    const dstPath = path.join(dst, entry.name);

    if (entry.isDirectory()) {
      await copyDirectory(srcPath, dstPath);  // Recurse into subdirectories
    } else {
      await fs.copyFile(srcPath, dstPath);     // Copy individual files
    }
  }
}

```

This implementation handles arbitrary nesting depth and maintains the original file hierarchy exactly as packaged.

## Integration with Main Process Startup

The sync runs **before** any renderer windows are created, ensuring extensions are available when the UI initializes. In [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts), the initialization sequence is:

```typescript
// electron/main/index.ts — startup sequence
import { app, BrowserWindow } from 'electron';
import { syncBuiltinExtensions } from './builtin-sync';
import { logger } from './logger';

app.whenReady().then(async () => {
  try {
    await syncBuiltinExtensions();  // Synchronize before UI loads
    logger.info('Extension sync completed');
  } catch (err) {
    logger.error('Extension sync failed, continuing with existing extensions:', err);
  }

  createMainWindow();  // Now safe to initialize UI
});

```

The `try/catch` wrapper ensures that sync failures — whether from permission errors, corrupted bundles, or disk space issues — never prevent the application from launching. Users see the last known good extension set rather than a broken startup.

## Error Handling and Logging Strategy

All sync operations report through Modly's centralized logger at [`electron/main/logger.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/logger.ts). The system distinguishes between:

- **Informational**: Version updates, successful completions
- **Warnings**: Missing version files (treated as "always update")
- **Errors**: Permission denials, I/O failures, malformed paths

Critical safety property: the sync routine never modifies user‑installed extensions. Because it operates on a dedicated subdirectory for built‑in extensions, third‑party extensions placed directly in `~/Modly/extensions` remain untouched even during full replacement cycles.

## Path Security with Extension‑Path Guard

A companion module at [`electron/main/extension-path-guard.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-path-guard.ts) validates all paths used during sync operations. This prevents directory traversal attacks where a maliciously crafted extension name might escape the intended sandbox. The guard checks:

- Path normalization (resolving `..` segments)
- Allowed base directory prefixes
- Symlink targets (when supported)

The sync mechanism invokes these guards before any `fs` operations, adding defense‑in‑depth to the file copying logic.

## Summary

- **Source location**: Bundled `resources/extensions` within the Electron app package
- **Target location**: `app.getPath('userData')/extensions` in the user's profile
- **Update detection**: [`builtin-version.txt`](https://github.com/lightningpixel/modly/blob/main/builtin-version.txt) comparison between source and destination
- **Replacement strategy**: Full directory removal and recursive copy when versions differ
- **Safety guarantees**: Try/catch isolation, user extension preservation, path validation via [`extension-path-guard.ts`](https://github.com/lightningpixel/modly/blob/main/extension-path-guard.ts)
- **Entry point**: [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) calls `syncBuiltinExtensions()` during `app.whenReady()`

## Frequently Asked Questions

### How does Modly handle extension updates without losing user data?

Modly stores built‑in extensions in a dedicated subdirectory separate from user‑installed extensions. The sync routine only replaces this subdirectory, leaving any manually added extensions intact. The version‑file mechanism ensures updates apply atomically: the old built‑in set is removed entirely and replaced with the new bundle.

### What happens if the sync fails due to permission errors?

The error is caught in [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) and logged via `logger.error()`, but the application continues launching. Users retain their existing extension directory contents. No partial writes occur because the replacement uses `fs.rm()` followed by full `copyDirectory()` — interrupted operations simply leave the directory missing, which triggers a fresh sync on the next restart.

### Can users disable the built‑in extension synchronization?

According to the source code in [`builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/builtin-sync.ts), there is no configuration flag to disable sync. The mechanism always runs during startup. Users who wish to modify built‑in extensions must do so after the sync completes, though their changes will be overwritten on the next application update that changes the version file.

### Where is the version file [`builtin-version.txt`](https://github.com/lightningpixel/modly/blob/main/builtin-version.txt) generated?

The version file is created at build time and embedded into `resources/extensions/` within the application bundle. Its content is typically a commit hash, package version, or content checksum. The sync routine treats any difference between source and target versions as a signal to perform full replacement.