# How Extensions Integrate with the Main Application in Modly

> Discover how Modly extensions integrate seamlessly as first-class plug-ins using Electron IPC, preload APIs, and reactive stores for a powerful, coordinated application experience.

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

---

**Modly treats extensions as first-class plug-ins that are discovered, managed, and executed through a coordinated system of Electron IPC channels, preload APIs, and reactive front-end stores.**

Modly is an open-source Electron-based application developed by lightningpixel/modly that implements a decoupled plugin architecture. Extensions live on disk as standalone packages and integrate with the main application through a multi-layered communication bridge. This architecture allows developers to add new model or process extensions without touching the core application code, utilizing asynchronous IPC calls and reactive state management.

## Architecture Overview: Six Layers of Integration

The integration follows a strict separation of concerns across six coordinated layers. Each layer handles a specific aspect of extension lifecycle management, from initial discovery on the file system to execution within workflow nodes.

### File System Discovery

The main process handles all file system operations to maintain security sandboxing. In [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts), the `ipcMain.handle('extensions:list', …)` handler (lines 945-1023) scans the `<userData>/extensions` directory and the built-in extensions folder. It filters out non-extension directories and constructs a manifest array containing metadata for each discovered extension.

### IPC Bridge Operations

The main process registers multiple IPC handlers for extension management between lines 945 and 1404 in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts). These include:

- `extensions:list` – Returns the manifest array
- `extensions:installFromGitHub` – Installs from a remote repository
- `extensions:uninstall` – Removes an extension
- `extensions:reload` – Refreshes the extension list
- `extensions:runProcess` – Executes extension logic

### Preload API Exposure

The renderer process cannot access Node.js APIs directly, so [`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts) (lines 196-244) exposes a typed JavaScript API under `window.electron.extensions.*`. This maps each IPC call to a method that the React UI can invoke safely.

### Front-End Store Management

The [`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts) file (lines 31-109) maintains the reactive state using Svelte writable stores. It consumes the preload API to fetch extension lists, handle installation progress, and update the UI when extensions are added or removed.

### Workflow Integration

When workflow nodes execute, [`src/areas/workflows/workflowRunStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/workflowRunStore.ts) (lines 281-439) resolves the extension via `getWorkflowExtension` and calls `window.electron.extensions.runProcess`. This bridges the visual workflow editor with the actual extension execution logic.

### Built-in Synchronization

On first launch, [`electron/main/builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/builtin-sync.ts) (lines 19-37) copies packaged extensions from app resources into `<userData>/builtin-extensions`, ensuring built-ins appear as regular user extensions in the discovery scan.

## The Data Flow: From UI to Execution

Understanding the runtime behavior requires tracing a complete operation from the renderer to the main process and back.

### Listing Available Extensions

When the application initializes, the front-end store triggers discovery:

```typescript
// src/shared/stores/extensionsStore.ts (excerpt)
await extensionsStore.reload();          // triggers window.electron.extensions.list()
const all = get(extensionsStore);        // Svelte store now contains the manifest

```

The `reload()` method calls `window.electron.extensions.list()`, which invokes the `extensions:list` IPC handler. The main process reads the directories and returns a JSON manifest array, which populates the Svelte store.

### Installing Extensions from GitHub

Extensions can be installed programmatically without manual file manipulation:

```typescript
await extensionsStore.installFromGitHub('https://github.com/user/my-modly-extension');

```

Under the hood, this executes `window.electron.extensions.installFromGitHub(url)`, triggering the `extensions:installFromGitHub` IPC handler in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts). The handler downloads, validates, and extracts the extension to the user data directory.

### Executing Process Extensions in Workflows

Workflow nodes reference extensions by `extensionId` and execute their processing logic:

```typescript
const result = await window.electron.extensions.runProcess(
  node.data.extensionId,                 // e.g. "my-extension"
  { text: "Hello world" },               // input payload
  { paramA: true }                       // optional parameters
);
if (!result.success) throw new Error(result.error);

```

The `runProcess` method sends the `extensions:runProcess` IPC message. The main process loads the extension code—whether JavaScript or Python—and executes its `process` entry point, returning a plain-object payload to the renderer.

### Reloading Extensions After Manual Changes

During development or after manual file edits, synchronize the state:

```typescript
await window.electron.extensions.reload();  // Sends 'extensions:reload' IPC
await extensionsStore.reload();             // Refresh UI store

```

The first call notifies the main process to rescan directories, while the second updates the reactive front-end store.

## Key Implementation Files

The extension system spans five critical files that define the contract between the main process and renderer:

- **[`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts)** – Central IPC definitions for all extension operations, including discovery, installation, and execution handlers.
- **[`electron/preload/electron-api.ts`](https://github.com/lightningpixel/modly/blob/main/electron/preload/electron-api.ts)** – Exposes the typed `window.electron.extensions` API to the renderer process.
- **[`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts)** – Front-end Svelte store managing extension manifests and UI state.
- **[`src/areas/workflows/workflowRunStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/workflowRunStore.ts)** – Executes workflow nodes by resolving extensions and invoking their process methods.
- **[`electron/main/builtin-sync.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/builtin-sync.ts)** – Copies built-in extensions into the user data directory on first launch.

## Summary

- **File-system discovery** occurs in the main process via `ipcMain.handle('extensions:list')` in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts), scanning both user and built-in directories.
- **IPC bridge** exposes all extension operations (list, install, uninstall, run) through asynchronous handlers that return plain JavaScript objects.
- **Preload API** provides a safe, typed interface at `window.electron.extensions.*`, allowing the React/Svelte front-end to invoke main process methods.
- **Reactive stores** in [`extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/extensionsStore.ts) maintain UI state and handle installation progress, uninstallation, and reload events.
- **Workflow execution** resolves extension IDs through [`workflowRunStore.ts`](https://github.com/lightningpixel/modly/blob/main/workflowRunStore.ts) and calls `runProcess` to execute extension logic within the main process.
- **Language agnosticism** allows extensions to be written in JavaScript or Python, as the IPC layer handles execution uniformly.

## Frequently Asked Questions

### How does Modly discover extensions at startup?

The main process scans the `<userData>/extensions` directory and the built-in extensions folder when the `extensions:list` IPC channel is invoked. This typically happens when `extensionsStore.reload()` is called during application initialization, returning a manifest array that populates the front-end store.

### What IPC channels manage extension lifecycle?

The [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts) file registers handlers for `extensions:list`, `extensions:installFromGitHub`, `extensions:uninstall`, `extensions:reload`, and `extensions:runProcess`. These channels handle discovery, installation, removal, refresh, and execution respectively.

### Can Modly extensions be written in languages other than JavaScript?

Yes. The architecture is language-agnostic at the IPC layer. While the preload API and stores are TypeScript/JavaScript, the `extensions:runProcess` handler in the main process can execute Python scripts or other runtime code, provided the extension implements the expected `process` entry point contract and returns a plain-object payload.

### How does the UI reflect real-time changes during extension installation?

The [`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts) manages reactive Svelte writable stores that update when IPC calls return new data. During installation, the store tracks progress states, and when the `installFromGitHub` handler completes, subsequent calls to `extensionsStore.reload()` fetch the updated manifest and re-render the component tree automatically.