How to Create a Custom Modly Extension: Complete Guide to Manifest Fields and Required Structure
Creating a custom Modly extension requires a properly configured manifest.json file with eight mandatory fields—id, name, type, version, author, description, entry, and nodes—plus model-specific fields when building model extensions.
Modly extensions are self-contained packages that extend the platform's workflow capabilities through custom processing nodes or AI model integrations. The extension system, as implemented in lightningpixel/modly, uses a strict manifest validation process that runs at installation time to ensure compatibility and security.
Overview of the Extension Creation Process
Extensions in Modly follow a predictable lifecycle: folder creation → manifest definition → entry point implementation → local testing → optional publication. The core validation logic resides in electron/main/ipc-handlers.ts, specifically within the parseExtensionManifest() function (lines 911–923), where each manifest undergoes schema verification before the extension becomes available in the UI.
Extensions can be loaded from two sources:
- Local folders under
~/.modly/extensions/or the app'sextensions/directory - Remote GitHub repositories via the Install from GitHub dialog
Step 1: Create the Extension Folder Structure
Every Modly extension lives as a discrete folder containing at minimum a manifest.json file and an entry point script.
# Create local extension directory
mkdir -p ~/.modly/extensions/my-awesome-ext
cd ~/.modly/extensions/my-awesome-ext
# Required files
touch manifest.json
touch processor.js # or your preferred entry filename
Modly scans registered extension directories at startup, parsing each manifest.json found in immediate subdirectories.
Step 2: Define Required Manifest Fields
The manifest.json file serves as the single source of truth for extension identity and capabilities. Based on the ParsedManifest type definition in electron/main/ipc-handlers.ts, these eight fields are mandatory for all extension types:
| Field | Type | Description |
|---|---|---|
id |
string | Unique identifier, conventionally author/extension-name |
name |
string | Human-readable display name shown in the UI |
type |
"process" | "model" |
Determines runtime behavior and validation rules |
version |
string | Semantic version (e.g., "1.0.0") |
author |
string | Creator name or organization |
description |
string | Short summary for extension browser |
entry |
string | Relative path to main JavaScript file |
nodes |
array | Workflow node definitions provided by this extension |
Complete Base Manifest Example
{
"id": "acme/text-processor",
"name": "Text Utilities",
"type": "process",
"version": "1.0.0",
"author": "Acme Corp",
"description": "Common text transformation operations",
"entry": "processor.js",
"nodes": [
{
"id": "uppercase",
"label": "To Uppercase",
"description": "Converts input text to uppercase",
"inputSchema": {
"type": "object",
"properties": {
"text": { "type": "string" }
}
},
"outputSchema": {
"type": "object",
"properties": {
"text": { "type": "string" }
}
}
}
]
}
Step 3: Implement Model-Specific Fields (When Applicable)
Model extensions require two additional fields beyond the base manifest. The extension-install-utils.ts file (line 37) explicitly validates these and throws an installation error if generator_class is absent:
generator_class: String name of the model generator class exported by the entry filenodeClass(optional): Custom node class identifier for specialized UI rendering
{
"id": "acme/custom-llm",
"name": "Custom Language Model",
"type": "model",
"version": "1.0.0",
"author": "Acme AI Lab",
"description": "Fine-tuned model for domain-specific tasks",
"entry": "generator.js",
"generator_class": "DomainSpecificLLM",
"nodes": [
{
"id": "generate",
"label": "Generate",
"description": "Run inference with custom model"
}
]
}
Step 4: Write the Entry Point Script
The file referenced by entry must export the appropriate interface based on extension type.
Process Extension Entry Point
For type: "process", export an async run function receiving { input, params }:
// processor.js
export async function run({ input, params }) {
const { text = '' } = input || {};
const { addPrefix = false, prefix = '' } = params || {};
const processed = addPrefix ? `${prefix}${text}` : text;
return {
text: processed,
charCount: processed.length,
wordCount: processed.split(/\s+/).filter(Boolean).length
};
}
The runtime execution path flows through workflowRunStore.ts (lines 428–440), where window.electron.extensions.runProcess() ultimately invokes this function.
Model Extension Entry Point
For type: "model", export the generator class matching generator_class:
// generator.js
export class DomainSpecificLLM {
constructor(config) {
this.config = config;
}
async generate(prompt, options = {}) {
// Model inference implementation
return {
text: `Generated response for: ${prompt}`,
tokensUsed: prompt.length / 4
};
}
async *stream(prompt, options = {}) {
// Streaming implementation for real-time output
yield { chunk: "Partial " };
yield { chunk: "response" };
}
}
Step 5: Define Node Specifications
Each object in the nodes array requires at minimum id and label. Full node schema enables rich workflow editor integration:
{
"nodes": [
{
"id": "transform",
"label": "Transform Text",
"description": "Applies configurable text transformations",
"inputSchema": {
"type": "object",
"required": ["text"],
"properties": {
"text": { "type": "string", "description": "Input text" }
}
},
"outputSchema": {
"type": "object",
"properties": {
"text": { "type": "string" },
"stats": { "type": "object" }
}
},
"paramSchema": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["uppercase", "lowercase", "reverse"],
"default": "uppercase"
}
}
}
}
]
}
Step 6: Test and Validate Locally
Install your extension through Modly's UI to verify manifest parsing:
- Open Extensions → Install from Local
- Select your extension folder
- Check for
manifestErrorindicators in the UI - Create a test workflow using your extension's nodes
Validation failures surface as error toasts with line references to parseExtensionManifest() in electron/main/ipc-handlers.ts.
Step 7: Publish via GitHub (Optional)
For distribution, push to a public repository and install via Install from GitHub. The installer (extension-install-utils.ts, lines 18–41) performs identical validation before registration:
# Repository structure for GitHub installation
my-modly-extension/
├── manifest.json
├── processor.js
└── README.md
Manifest Validation in Source Code
The validation pipeline enforces field presence through explicit checks:
| File | Function | Lines | Purpose |
|---|---|---|---|
electron/main/ipc-handlers.ts |
parseExtensionManifest() |
911–923 | Schema validation, required field checks |
electron/main/extension-install-utils.ts |
installExtension() |
18–41 | Installation orchestration, model field verification |
electron/main/extension-install-utils.ts |
Model loader | 37 | generator_class presence check |
Summary
- Eight fields are mandatory in every Modly extension manifest:
id,name,type,version,author,description,entry, andnodes - Model extensions require
generator_classin addition to base fields - Process extensions export an async
run({ input, params })function - Model extensions export a class matching the
generator_classname - Validation occurs in
parseExtensionManifest()before any extension becomes available - Local testing uses Install from Local; distribution uses Install from GitHub
Frequently Asked Questions
What happens if I omit a required manifest field?
Modly rejects the extension during installation with a validation error. The parseExtensionManifest() function in electron/main/ipc-handlers.ts checks for all mandatory fields and returns a structured error object that the UI displays as a manifestError notification with specific field references.
Can I use a different entry filename than processor.js?
Yes. The entry field accepts any relative path within your extension folder. Process extensions default to processor.js only when entry is omitted, but explicit declaration is recommended for clarity. The runtime resolves this path relative to the extension's root directory.
How do I debug a failing extension installation?
Check the DevTools console for validation errors from parseExtensionManifest(). Enable verbose logging in Modly's settings to see full manifest parsing traces. Common failures include malformed JSON, missing generator_class for model types, or node definitions lacking required id and label properties.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →