# How Built-In Extensions Are Synchronized in the Modly Electron Main Process

> Learn how Modly synchronizes built-in extensions in the Electron main process. Discover the syncBuiltinExtensions function and app.whenReady event.

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

---

**Modly synchronizes built-in extensions by copying them from the application bundle to a writable user data directory via the `syncBuiltinExtensions()` function during the Electron `app.whenReady()` lifecycle event.**

The `lightningpixel/modly` repository ships core functionality as built-in extensions bundled within the application. Because the renderer process requires write access to extension directories, the Electron main process performs a mandatory one-way synchronization on every startup, mirroring the bundled resources into the user's data folder.

## The Synchronization Lifecycle

The synchronization logic follows a strict sequence defined in [`electron/main/builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/builtin-sync.ts). This ensures the built-in extensions are always current with the application version while preventing file corruption or stale assets.

### Source Directory Resolution

The `getBuiltinResourcesDir()` function determines where to read the built-in extensions from based on the execution context. According to lines 10-15 of [`electron/main/builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/builtin-sync.ts), the logic branches as follows:

- **Packaged applications**: Extensions reside at `process.resourcesPath/builtin-extensions`
- **Development mode**: Extensions are compiled to `out/builtin-extensions` relative to the project root

### Destination Directory Setup

The destination is always computed by `getBuiltinExtensionsDir()` (lines 6-8), which returns a path inside the user's data directory:

```typescript
// Resolved to <userData>/builtin-extensions
const destDir = getBuiltinExtensionsDir();

```

This uses Electron's `app.getPath('userData')` to ensure the renderer has appropriate write permissions.

### Atomic Copy and Cleanup Strategy

The `syncBuiltinExtensions()` function implements a destructive copy pattern to guarantee consistency. As implemented in lines 32-37:

1. **Validation**: If the source directory does not exist, synchronization skips silently (supporting builds that omit built-ins)
2. **Cleanup**: The destination directory is wiped entirely using `rmSync` to eliminate removed or outdated extensions
3. **Copy**: The entire tree is recreated using `cpSync` to copy recursively from source to destination

This overwrite strategy ensures no stale extension files persist across version updates.

### Startup Trigger

The synchronization is triggered automatically during application initialization. In [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) (lines 89-103), the call is positioned inside the `app.whenReady()` promise handler:

- It executes after the Chromium cache is cleared
- It completes before the Python backend bridge initializes
- It precedes the creation of the main UI window

This sequencing guarantees the extensions are available before the renderer process attempts to load them.

## IPC Integration and Extension Discovery

Once synchronized, built-in extensions are exposed to the renderer alongside user-installed extensions. The IPC handler `extensions:list` in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts) (lines 45-50) aggregates both sources by reading:

1. The user extensions directory
2. The built-in directory returned by `getBuiltinExtensionsDir()`

The renderer receives a unified list with built-in entries typically listed first.

## Implementation Code Examples

### Manual Triggering

While automatic, you can invoke synchronization manually during main process execution:

```typescript
import { syncBuiltinExtensions } from './builtin-sync';

// Force re-synchronization of built-in extensions
syncBuiltinExtensions();

```

### Reading Extensions from the Renderer

Access the synchronized extensions via the IPC channel:

```typescript
// Renderer process
const allExtensions = await window.electronAPI.invoke('extensions:list');
console.log(allExtensions); // Built-ins appear first, followed by user extensions

```

### Lifecycle Integration

The automatic trigger occurs during the standard Electron bootstrap sequence:

```typescript
app.whenReady().then(async () => {
  // ... cache clearing and setup ...
  
  await syncBuiltinExtensions();  // Copy built-ins to user data
  
  // ... initialize Python backend, create window ...
});

```

## Summary

- **Built-in extensions** are bundled in the application resources at `process.resourcesPath/builtin-extensions` (production) or `out/builtin-extensions` (development)
- **Synchronization** occurs via `syncBuiltinExtensions()` in [`electron/main/builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/builtin-sync.ts)
- **Destination** is always `<userData>/builtin-extensions` to ensure write access
- **Cleanup** uses `rmSync` to wipe the destination before `cpSync` copies the new tree, preventing stale files
- **Timing** is controlled in [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts) during `app.whenReady()`, after cache clearing but before backend initialization
- **Discovery** combines built-in and user extensions through the `extensions:list` IPC handler in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts)

## Frequently Asked Questions

### When does Modly synchronize built-in extensions?

Modly triggers synchronization once during every application startup, specifically inside the `app.whenReady()` event handler in [`electron/main/index.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/index.ts). This occurs before the Python backend and UI window are created, ensuring extensions are ready when the renderer process loads.

### Why does Modly delete the destination directory before copying?

The `syncBuiltinExtensions()` function uses `rmSync` to wipe `<userData>/builtin-extensions` before calling `cpSync` to prevent stale, renamed, or removed extensions from persisting across version updates. This guarantees the destination exactly mirrors the current application bundle.

### How can renderer processes access the synchronized built-in extensions?

The renderer accesses built-ins through the `extensions:list` IPC channel defined in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts). This handler reads both the user extensions directory and the synchronized built-in directory, returning a combined array where built-in extensions are listed first.

### What happens if the built-in extensions source directory is missing?

If `getBuiltinResourcesDir()` returns a path that does not exist (such as in custom builds that omit built-in extensions), `syncBuiltinExtensions()` skips the copy operation silently without throwing an error, allowing the application to start normally without built-in extensions.