Node graph workflow document utilities and a controlled React workbench for editing workflow graphs.
bun add @moritzbrantner/workflow-editorThe React workbench expects react as a peer dependency and consumes
@moritzbrantner/ui@^0.8.0.
WorkflowWorkbenchfor a React node graph editor built on@moritzbrantner/ui.WorkflowEditorfor a browser-first workflow editor shell with document library controls, local saving/loading, JSON import/export, explicit versions, and undo/redo.defaultWorkflowEditorNodeTemplates,workflowEditorControlFlowNodeTemplates,workflowEditorJsonNodeTemplates, andworkflowEditorCollectionNodeTemplatesfor built-in control-flow, JSON value, and collection transform nodes.normalizeWorkflowEditorDocument(...),connectWorkflowEditorNodes(...),duplicateWorkflowEditorNode(...), and node/edge mutation helpers.copyWorkflowEditorSelection(...),pasteWorkflowEditorClipboardPayload(...),duplicateWorkflowEditorSelection(...), andremoveWorkflowEditorSelection(...)for headless selected-subgraph clipboard operations.layoutWorkflowEditorDocument(...)for deterministic automatic graph layout powered by Dagre.createWorkflowEditorComposedNode(...),composeWorkflowEditorNodes(...),restoreWorkflowEditorComposedNode(...), andhasWorkflowEditorNodeComposition(...)for reusable component-style nodes backed by embedded subgraphs.updateWorkflowEditorNodeWorkflowReference(...),getWorkflowEditorReferencedDocumentIds(...), andgetWorkflowEditorReferenceDiagnostics(...)for reference-based nested workflow support.validateWorkflowEditorConnection(...),detectWorkflowEditorCycles(...),topologicallySortWorkflowEditorNodes(...), and UI adapter helpers.validateWorkflowEditorDocument(...),assertWorkflowEditorDocument(...), andWorkflowEditorDocumentValidationErrorfor strict document validation diagnostics.createWorkflowEditorLibrary(...),createLocalStorageWorkflowEditorStorage(...),buildWorkflowEditorDocumentFile(...), andparseWorkflowEditorDocumentFile(...)for headless persistence.createWorkflowEditorHistory(...),commitWorkflowEditorHistory(...),undoWorkflowEditorHistory(...), andredoWorkflowEditorHistory(...)for headless transaction history.encodeWorkflowEditorSharePayload(...)anddecodeWorkflowEditorSharePayload(...)for dependency-free share tokens.
import "@moritzbrantner/ui/styles.css";
import {
WorkflowEditor,
createWorkflowEditorEntry,
createWorkflowEditorLibrary,
defaultWorkflowEditorNodeTemplates,
normalizeWorkflowEditorDocument,
} from "@moritzbrantner/workflow-editor";
const initialLibrary = createWorkflowEditorLibrary({
documents: [
createWorkflowEditorEntry({
id: "demo",
name: "Demo workflow",
document: normalizeWorkflowEditorDocument({
nodes: [],
edges: [],
}),
}),
],
activeDocumentId: "demo",
});
export function App() {
return (
<WorkflowEditor
storageKey="my-product.workflow-editor"
initialLibrary={initialLibrary}
nodeTemplates={defaultWorkflowEditorNodeTemplates}
/>
);
}The default editor shell is still available, but the React API also exposes controller hooks and movable chrome components for custom layouts.
import {
WorkflowEditorOverviewPanel,
WorkflowEditorSettingsPanel,
WorkflowEditorDocumentMenu,
WorkflowWorkbenchCanvas,
WorkflowWorkbenchInspector,
WorkflowWorkbenchPalette,
useWorkflowEditorController,
} from "@moritzbrantner/workflow-editor";
export function CustomWorkflowEditor(props) {
const controller = useWorkflowEditorController(props);
return (
<div className="grid h-screen grid-cols-[18rem_minmax(0,1fr)_22rem] grid-rows-[auto_1fr]">
<header className="col-span-3">
<WorkflowEditorDocumentMenu controller={controller} />
<WorkflowEditorSettingsPanel controller={controller} />
</header>
<WorkflowWorkbenchPalette controller={controller.workbench} />
<WorkflowWorkbenchCanvas controller={controller.workbench} />
<aside className="grid min-h-0 gap-3 overflow-y-auto">
<WorkflowWorkbenchInspector controller={controller.workbench} />
<WorkflowEditorOverviewPanel controller={controller} />
</aside>
</div>
);
}Settings and catalogs are controlled by the host app. Scalar editor props remain supported; when
both a scalar prop and settings.editor provide the same value, the scalar prop wins.
const [settings, setSettings] = useState({
editor: {
compactControls: false,
enableNestedWorkflows: true,
maxNestedWorkflowDepth: 64,
maxVersions: 10,
readOnly: false,
showDocumentPath: true,
showDocumentStats: true,
showWorkbenchStats: true,
},
app: { environment: "staging" },
});
const [typeDefinitions, setTypeDefinitions] = useState([]);
const [nodeTemplates, setNodeTemplates] = useState(defaultWorkflowEditorNodeTemplates);
const controller = useWorkflowEditorController({
settings,
settingsFields: [
{
key: "environment",
label: "Environment",
kind: "select",
options: [
{ value: "staging", label: "Staging" },
{ value: "production", label: "Production" },
],
},
],
onSettingsChange: setSettings,
typeDefinitions,
onTypeDefinitionsChange: setTypeDefinitions,
nodeTemplates,
onNodeTemplatesChange: setNodeTemplates,
});For smaller changes, use slots on the existing components:
<WorkflowEditor
{...props}
chrome={{
documentControls: (controller) => (
<header>
<WorkflowEditorDocumentMenu controller={controller} />
</header>
),
palette: "hidden",
}}
/>-
The package also exposes
@moritzbrantner/workflow-editor/coreand@moritzbrantner/workflow-editor/reactsubpaths. -
Persistence, history, share, and editor shell helpers are also available through
@moritzbrantner/workflow-editor/persistence,@moritzbrantner/workflow-editor/history,@moritzbrantner/workflow-editor/share, and@moritzbrantner/workflow-editor/editor. -
The package owns workflow document state and graph validation;
@moritzbrantner/uisupplies the generic graph surface and inspector controls. -
Node templates can set
categoryorcategoryPathto group the node palette into flat categories or nested category trees. -
normalizeWorkflowEditorDocument(...)validates strictly by default and throwsWorkflowEditorDocumentValidationErrorwhen nodes, edges, ids, endpoints, or graph cycles are invalid. UsevalidateWorkflowEditorDocument(...)to inspect diagnostics without throwing. -
To accept older or partially invalid graph data and preserve the previous pruning behavior, use repair mode:
const document = normalizeWorkflowEditorDocument(importedDocument, { mode: "repair" });
Repair mode normalizes node coordinates and viewport values, removes dangling/self/cycle-forming edges, and syncs built-in object-constructor and object-decomposition nodes.
-
Imported workflow JSON files are validated strictly. Malformed workflow documents fail with validation diagnostics instead of being silently repaired.
-
Workflow ports use serializable TypeScript-like
typeobjects instead of port-levelkindstrings.validateWorkflowEditorConnection(...)accepts an optionaltypeDefinitionsregistry and allows an output to connect to an input when the output type is assignable to the input type. -
Nested workflows are reusable document references stored on nodes as
workflowRef: { documentId }. References may point to any document, including the current document. The editor shell supports drill-in breadcrumbs with a finitemaxNestedWorkflowDepthguard; graph DAG validation remains scoped to edges inside each individual document. -
Composed workflow nodes embed a normalized subgraph on the node as
composition.composeWorkflowEditorNodes(...)wraps selected nodes, exposes boundary ports for unconnected or externally connected inputs/outputs, and reroutes external edges through the wrapper.restoreWorkflowEditorComposedNode(...)expands the wrapper back into ordinary nodes and edges. -
WorkflowWorkbenchsupports multi-select, copy/paste, duplicate, delete, and arrange actions. UseselectedNodeIds,selectedEdgeIds, andonSelectionStateChange(...)for controlled multi-selection; the olderselectedNodeId,selectedEdgeId, andonSelectionChange(...)props remain available for single-selection integrations.
Tests and benchmarks default to one worker to keep memory use predictable. Override the cap with
WORKFLOW_EDITOR_WORKERS, or use the narrower WORKFLOW_EDITOR_TEST_WORKERS and
WORKFLOW_EDITOR_PLAYWRIGHT_WORKERS variables when needed.
WORKFLOW_EDITOR_WORKERS=2 bun run test
WORKFLOW_EDITOR_WORKERS=2 bun run bench
WORKFLOW_EDITOR_PLAYWRIGHT_WORKERS=2 bun run test:playwrightStorybook is available for the React workbench and editor shell:
bun run storybook
bun run storybook:buildUnit, integration, e2e, and Storybook files are colocated in src next to the source surface they
cover. Root config files remain at the repository root because they configure the package-wide
tooling.
import {
copyWorkflowEditorSelection,
layoutWorkflowEditorDocument,
pasteWorkflowEditorClipboardPayload,
} from "@moritzbrantner/workflow-editor";
const selection = {
nodeIds: ["source", "transform"],
edgeIds: [],
primary: { type: "node", id: "transform" },
};
const clipboard = copyWorkflowEditorSelection(document, selection);
const pasted = pasteWorkflowEditorClipboardPayload(document, clipboard);
const arranged = layoutWorkflowEditorDocument(pasted.document, {
direction: "right",
});Layout is also available from the focused subpath:
import { layoutWorkflowEditorDocument } from "@moritzbrantner/workflow-editor/layout";- Configurable graph validation policies.
- Port cardinality rules for single-input and multi-input ports.
- Typed node template registries with validation.
- Accessibility coverage for keyboard-only graph editing.
- Demo pages for pipeline, automation, and branching workflow examples.