How Extensions Integrate with the Main Application in Modly

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, 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. 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 (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 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 (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 (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:

// 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:

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. 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:

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:

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:

Summary

  • File-system discovery occurs in the main process via ipcMain.handle('extensions:list') in 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 maintain UI state and handle installation progress, uninstallation, and reload events.
  • Workflow execution resolves extension IDs through 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →