# How Extension Manifests Define Model and Process Extensions in Modly

> Discover how Modly extension manifests use JSON to define AI models and custom processing logic. Learn about entry points, node behaviors, and metadata for seamless integration.

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

---

**Extension manifests in Modly are JSON configuration files that declare whether an extension provides AI models or custom processing logic, defining entry points, node behaviors, and metadata that the application parses at startup to populate the extensions store and workflow engine.**

In the `lightningpixel/modly` repository, every plugin is distributed as a folder containing a mandatory [`manifest.json`](https://github.com/lightningpixel/modly/blob/main/manifest.json) file. This manifest serves as the single source of truth that tells Modly whether the extension contributes AI model capabilities or custom processing workflows. By parsing these manifests at runtime, Modly builds a unified registry that powers both the extension manager UI and the visual workflow generator.

## How Modly Scans and Parses Extension Manifests

When Modly initializes or when a user triggers the extensions list command, the application executes a systematic discovery process across both user-installed and built-in extension directories.

### Scanning Extension Directories

The main process begins by scanning two locations simultaneously using `readExtensionsFromDir()` in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts). The function checks both the user-supplied extensions folder and the built-in extensions directory:

```typescript
// electron/main/ipc-handlers.ts → readExtensionsFromDir()
// (lines ≈ 53-71)
const [userExts, builtinExts] = await Promise.all([
    readExtensionsFromDir(extensionsDir, false),
    readExtensionsFromDir(builtinDir,    true),
]);

```

For each candidate folder, Modly looks for either [`manifest.json`](https://github.com/lightningpixel/modly/blob/main/manifest.json) or [`package.json`](https://github.com/lightningpixel/modly/blob/main/package.json). If found, the raw JSON is fed into the parsing pipeline.

### Validating and Normalizing Manifest Data

The `parseExtensionManifest()` function (lines ≈ 11-44 in the same file) transforms the raw JSON into normalized `ModelExtension` or `ProcessExtension` objects. This function handles the `type` discrimination logic:

```typescript
// electron/main/ipc-handlers.ts → parseExtensionManifest()
function parseExtensionManifest(parsed, fallbackId, trustedRepos, builtin = false) {
    const common = { … };
    const nodes = (parsed.nodes ?? []).map(…);
    if (parsed.type === 'process') {
        return { …common, type: 'process', entry: parsed.entry ?? 'processor.js', nodes };
    }
    return { …common, type: 'model', nodes };
}

```

The resulting objects are stored in the **extensions store** ([`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts)), which exposes `modelExtensions` and `processExtensions` arrays consumed by the React UI via the `useExtensionsStore` hook.

## Model Extensions vs. Process Extensions

Modly distinguishes between two extension types based on the `type` field in the manifest. While both share the same **node schema** (`ExtensionNode` defined in [`src/shared/types/electron.d.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/types/electron.d.ts)), they differ in execution semantics and required fields.

| Property | Model Extension | Process Extension |
|----------|----------------|-------------------|
| `type` | `'model'` | `'process'` |
| `entry` | None—models are collections of nodes that produce assets | Path to the JavaScript or Python entry point (defaults to [`processor.js`](https://github.com/lightningpixel/modly/blob/main/processor.js)) |
| `nodes` | Describe input-to-output behavior for AI inference (e.g., text-to-image) | Describe processing steps for custom operations (e.g., mesh optimization) |
| Required fields | `id`, `name`, `type='model'`, `nodes` | Same as model, plus `entry` and `type='process'` |

### Structure of a Model Extension Manifest

A minimal model manifest defines nodes that map inputs to outputs without an execution entry point:

```json
{
  "id": "my-diffusion",
  "name": "My Diffusion Model",
  "type": "model",
  "nodes": [
    {
      "id": "txt2img",
      "name": "Text-to-Image",
      "input": "text",
      "output": "image",
      "params_schema": [
        { "id": "prompt", "label": "Prompt", "type": "string", "default": "" }
      ]
    }
  ]
}

```

### Structure of a Process Extension Manifest

Process extensions require an `entry` field specifying the script to invoke when the workflow runs:

```json
{
  "id": "mesh-opt",
  "name": "Mesh Optimizer",
  "type": "process",
  "entry": "processor.js",
  "nodes": [
    {
      "id": "optimize",
      "name": "Optimize Mesh",
      "input": "mesh",
      "output": "mesh",
      "params_schema": [
        { "id": "target_faces", "label": "Target Faces", "type": "int", "default": 5000 }
      ]
    }
  ]
}

```

## Consuming Extension Data in the UI and Workflow Engine

Once parsed, extension manifests power both the extension management interface and the visual workflow builder.

### Accessing Extensions via the Zustand Store

The UI retrieves loaded extensions through the extensions store. Components call `loadExtensions()` to trigger a refresh and access the partitioned arrays:

```typescript
import { useExtensionsStore } from '@shared/stores/extensionsStore';

function ExtensionList() {
  const { modelExtensions, processExtensions, loadExtensions } = useExtensionsStore();
  useEffect(() => { loadExtensions(); }, []);
  // Render badges based on `type` field
}

```

### Building Workflow-Ready Nodes

The workflow generator consumes both extension types through `buildAllWorkflowExtensions()` in [`src/areas/workflows/mockExtensions.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/mockExtensions.ts). This function iterates over `modelExtensions` and `processExtensions`, transforming each node definition into a `WorkflowExtension` object that the canvas UI can render:

```typescript
import { buildAllWorkflowExtensions } from '@areas/workflows/mockExtensions';

const workflowExts = buildAllWorkflowExtensions(modelExtensions, processExtensions);
// workflowExts contains unified objects with type: 'model' | 'process'

```

## Handling Manifest Errors and Validation

During installation, Modly validates extension manifests to prevent corrupted plugins from crashing the application. If a manifest is missing, unparsable, or the installation never completed, Modly records a `manifestError` property on the extension object. This field accepts one of three literal values: `'missing'`, `'invalid'`, or `'incomplete'`. The UI checks this property to display warning badges and disable broken extensions.

## Summary

- **Extension manifests** ([`manifest.json`](https://github.com/lightningpixel/modly/blob/main/manifest.json)) are mandatory configuration files that declare an extension's identity, type, and node definitions in the `lightningpixel/modly` architecture.
- **Model extensions** define AI inference nodes without execution entry points, while **process extensions** specify a script `entry` point for custom processing logic.
- The **`parseExtensionManifest()`** function in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts) normalizes raw JSON into typed `ModelExtension` or `ProcessExtension` objects.
- Parsed extensions populate a **Zustand store** ([`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts)) that provides reactive data to the React UI and workflow engine.
- **Error states** (`manifestError`) track corrupted installations, allowing the UI to gracefully handle missing or invalid manifests.

## Frequently Asked Questions

### What fields are required in a Modly extension manifest?

Every manifest must include `id`, `name`, `type` (either `'model'` or `'process'`), and a `nodes` array defining the extension's interface. Process extensions additionally require an `entry` field specifying the path to the processor script, which defaults to [`processor.js`](https://github.com/lightningpixel/modly/blob/main/processor.js) if omitted.

### How does Modly distinguish between model and process extensions?

Modly checks the `type` field during the `parseExtensionManifest()` execution in [`electron/main/ipc-handlers.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/ipc-handlers.ts). When `type` equals `'process'`, the parser includes an `entry` property in the resulting object; otherwise, it returns a `ModelExtension` without execution logic. Both types share the same `ExtensionNode` schema for defining inputs, outputs, and parameters.

### Where does Modly store parsed extension data?

Parsed extension objects are stored in the extensions store located at [`src/shared/stores/extensionsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/extensionsStore.ts). This Zustand-based store maintains separate arrays for `modelExtensions` and `processExtensions`, which components access via the `useExtensionsStore()` hook. The store persists the unified registry across the application lifecycle.

### What happens if an extension manifest is invalid?

If `readExtensionsFromDir()` encounters a missing, unparsable, or incomplete manifest, it flags the extension with a `manifestError` property set to `'missing'`, `'invalid'`, or `'incomplete'`. The UI consumes this metadata to display error states and prevent users from activating corrupted extensions in workflows.