# How Modly Migrates Legacy Workflow Formats to the Node-Based System

> Modly automatically migrates legacy block-list workflows to its node-based system. Discover how the migrateWorkflow function ensures seamless backward compatibility and modernizes your processes.

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

---

**Modly automatically detects legacy block-list workflows and converts them to directed graphs of nodes and edges using the `migrateWorkflow()` function in [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts), ensuring seamless backward compatibility across versions.**

Modly is an open-source workflow editor built on React Flow that stores workflows as directed graphs. Early versions of the application used a simpler *block-list* format consisting of an array of extension blocks, while the current architecture requires explicit `nodes` and `edges` arrays. When a workflow is loaded, Modly transparently migrates any legacy-format objects to the modern node-based representation before they enter the UI store, allowing users to edit historic files without manual conversion.

## Legacy vs. Node-Based Workflow Formats

The evolution from Modly's early architecture to its current React Flow implementation required a fundamental shift in how workflows are serialized.

**Legacy Block-List Format:** Early workflow files stored operations as a sequential `blocks` array, where each block represented an extension with parameters. This format optionally included an `input` field (defaulting to `'image'`) but lacked explicit connection topology.

**Modern Node-Based Format:** Current workflows are directed graphs containing:
- **`nodes`**: An array of `WFNode` objects including input nodes and extension nodes
- **`edges`**: An array of `WFEdge` objects defining connections between node handles

The migration bridge between these formats lives entirely within the workflow store layer.

## The Migration Pipeline in [`workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/workflowsStore.ts)

All migration logic resides in [`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts). The system performs detection and conversion automatically when workflows are loaded from disk.

### Legacy Detection in the `load()` Method

The entry point for migration is the `load()` method (lines 81-90). This method fetches workflow entries via the Electron bridge (`window.electron.workflows.list()`) and iterates through the results, casting each entry as `LegacyWorkflow`:

```typescript
for (const entry of raw as LegacyWorkflow[]) {
    const wf = migrateWorkflow(entry)   // Migrates if needed
}

```

If the retrieved workflow already contains `nodes` and `edges` fields, `migrateWorkflow()` passes it through to sanitization. Otherwise, it triggers the full block-to-node conversion pipeline.

### The `migrateWorkflow()` Entry Point

The `migrateWorkflow(raw: LegacyWorkflow): Workflow` function (lines 26-66) serves as the primary conversion engine. It first checks for modern format indicators:

```typescript
// Lines 26-30: Early return for already-migrated workflows
if (raw.nodes && raw.edges) {
    return { ...raw, edges: sanitizeEdges(raw.edges, raw.nodes) }
}

```

If these fields are absent, the function proceeds to construct a new graph from the legacy `blocks` array.

### Constructing Nodes from Legacy Blocks

The node construction phase (lines 32-48) transforms sequential blocks into a graph structure:

1. **Input Node Creation** (lines 36-41): The function always generates an input node first, using the workflow's `input` field or defaulting to `'image'`.

2. **Extension Node Mapping** (lines 43-48): Each object in the legacy `blocks` array is converted to an extension node with preserved `id`, `extension` type, `enabled` status, and `params`.

These nodes are concatenated into `allNodes`, maintaining the original execution order as spatial positioning data.

### Edge Wiring and Sanitization

After node construction, the system generates connections (lines 52-56) to create a linear processing chain:

```typescript
// Connect each node to its successor
const edges: WFEdge[] = []
for (let i = 0; i < allNodes.length - 1; i++) {
    edges.push(createEdge(allNodes[i], allNodes[i + 1]))
}

```

The helper function `sanitizeEdges` (lines 4-24) then validates the edge list by removing dangling references to non-existent nodes and patching missing handle identifiers. This ensures graph integrity regardless of the source format.

The final function returns a complete `Workflow` object (lines 58-66) containing `id`, `name`, `description`, the constructed `nodes` and `edges`, and preserved timestamps.

## Programmatic Migration Example

You can invoke the migration logic directly for batch conversions or testing. The following example demonstrates converting a legacy block-list workflow to the node-based format:

```typescript
import { migrateWorkflow } from '@/shared/stores/workflowsStore'

// Example legacy workflow JSON (as read from disk)
const legacy = {
  id: 'w123',
  name: 'Legacy Example',
  description: '',
  input: 'image',
  blocks: [
    { id: 'b1', extension: 'stable-diffusion', enabled: true, params: {} },
    { id: 'b2', extension: 'mesh-optimizer', enabled: true, params: {} },
  ],
  createdAt: '2024-01-01T00:00:00Z',
  updatedAt: '2024-01-02T00:00:00Z',
}

// Convert to the node-based format
const workflow = migrateWorkflow(legacy)

// `workflow.nodes` now contains an input node + two extension nodes
// `workflow.edges` contains linear connections between them
console.log(workflow)

```

To load workflows through the UI store with automatic migration:

```typescript
await useWorkflowsStore.getState().load()
// Every entry in the store has been passed through migrateWorkflow()

```

## Key Files in the Migration Architecture

The migration system spans several files in the `lightningpixel/modly` repository:

- **[`src/shared/stores/workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/stores/workflowsStore.ts)**: Core store containing `migrateWorkflow()`, edge sanitization logic, and the `load()` method that triggers conversion.

- **`src/shared/stores/workflowsStore.test.mjs`**: Unit tests verifying that legacy block-format workflows correctly transform into valid node and edge arrays.

- **[`src/shared/types/electron.d.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/types/electron.d.ts)**: TypeScript definitions for `Workflow`, `WFNode`, and `WFEdge` that constrain the migration output.

- **[`src/areas/workflows/workflowRunStore.ts`](https://github.com/lightningpixel/modly/blob/main/src/areas/workflows/workflowRunStore.ts)**: Consumes migrated workflow objects when executing runs, expecting the modern node-based structure.

Additionally, `readStoredFolders()` in the workflows store contains a separate legacy guard (lines 49-53) that converts old plain-string folder arrays to the current object-based folder schema, ensuring persistence-layer compatibility.

## Summary

- **Automatic Detection**: The `load()` method in [`workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/workflowsStore.ts) identifies legacy workflows by checking for the absence of `nodes` and `edges` arrays.
- **Transparent Conversion**: The `migrateWorkflow()` function transforms block-list formats into React Flow-compatible directed graphs without user intervention.
- **Node Construction**: Legacy blocks become extension nodes prepended by an automatic input node (defaulting to `'image'`).
- **Linear Edge Generation**: The system generates sequential connections between nodes and sanitizes them to remove invalid references.
- **Backward Compatibility**: Both historic block-format files and modern node-based workflows coexist seamlessly in the UI.

## Frequently Asked Questions

### What triggers the legacy workflow migration in Modly?

The migration triggers automatically when `useWorkflowsStore.load()` fetches workflows from disk via the Electron bridge. The system casts raw entries as `LegacyWorkflow` types and passes them through `migrateWorkflow()`, which detects legacy formats by checking for the presence of `nodes` and `edges` properties. If these are missing, the conversion pipeline executes before the data reaches the UI store.

### How does Modly handle missing input fields in legacy workflows?

When processing legacy workflows, the migration logic defaults the input node type to `'image'` if the legacy `input` field is undefined or omitted (lines 36-41 in [`workflowsStore.ts`](https://github.com/lightningpixel/modly/blob/main/workflowsStore.ts)). This ensures that every migrated workflow has a valid entry point node connecting to the first extension block, maintaining graph connectivity even for incomplete legacy files.

### Can I manually migrate workflows outside the UI?

Yes. You can import `migrateWorkflow` directly from `@/shared/stores/workflowsStore` and invoke it programmatically with a legacy workflow object. This is useful for batch migrating file collections or testing conversion logic without running the full Electron application. The function returns a fully valid `Workflow` object compatible with Modly's current React Flow renderer.

### Where are the migration unit tests located?

Unit tests verifying the legacy-to-node migration logic reside in `src/shared/stores/workflowsStore.test.mjs`. These tests validate that block arrays convert correctly to node lists, that edges form proper linear chains, and that `sanitizeEdges` correctly removes dangling references when nodes are deleted or reordered during migration.