Skip to content

Custom Nodes

FlowDrop ships with 7 built-in node types, but you can register your own custom node components to extend the editor.

There are three registration methods:

ApproachWhen to use
registerCustomNode()One-off project-specific nodes
registerFlowDropPlugin()Libraries providing multiple node types
createPlugin()Same as above, with a chainable builder API

Custom node types are namespaced (e.g., "mylib:code-editor") to prevent conflicts.

1. Write a Svelte component:

CodeEditorNode.svelte
<script lang="ts">
import type { NodeComponentProps } from '@flowdrop/flowdrop/editor';
let { data, selected = false }: NodeComponentProps = $props();
</script>
<div class="code-editor-node" class:selected>
<h4>{data.label}</h4>
<pre>{data.config?.code ?? ''}</pre>
</div>
<style>
.code-editor-node {
padding: 12px;
border: 1px solid var(--fd-border);
border-radius: 6px;
background: var(--fd-surface);
min-width: 200px;
}
.code-editor-node.selected {
border-color: var(--fd-primary);
}
</style>

2. Register it:

import { registerCustomNode } from '@flowdrop/flowdrop/editor';
import CodeEditorNode from './CodeEditorNode.svelte';
registerCustomNode('myapp:code-editor', 'Code Editor', CodeEditorNode, {
icon: 'mdi:code-braces',
description: 'A custom code editor node',
category: 'custom'
});

3. Make it available in the sidebar by passing NodeMetadata with a matching type:

const app = await mountFlowDropApp(container, {
nodes: [
{
id: 'myapp:code-editor',
name: 'Code Editor',
type: 'myapp:code-editor', // Must match registered type
description: 'Write and edit code',
category: 'processing',
inputs: [{ id: 'input', name: 'Input', type: 'input', dataType: 'string' }],
outputs: [{ id: 'output', name: 'Output', type: 'output', dataType: 'string' }]
}
]
});

All custom node components must accept:

interface NodeComponentProps {
data: {
label: string;
config: Record<string, unknown>;
metadata: NodeMetadata;
nodeId?: string;
executionInfo?: NodeExecutionInfo;
onConfigOpen?: (node) => void;
};
selected?: boolean;
isProcessing?: boolean;
isError?: boolean;
}

Register multiple nodes under a shared namespace:

import { registerFlowDropPlugin } from '@flowdrop/flowdrop/editor';
const result = registerFlowDropPlugin({
namespace: 'awesome',
name: 'Awesome Nodes',
version: '1.0.0',
nodes: [
{
type: 'fancy',
displayName: 'Fancy Node',
component: FancyNode,
icon: 'mdi:sparkles'
},
{
type: 'glow',
displayName: 'Glowing Node',
component: GlowNode,
icon: 'mdi:lightbulb'
}
]
});
// result.registeredTypes: ["awesome:fancy", "awesome:glow"]
import { createPlugin } from '@flowdrop/flowdrop/editor';
createPlugin('awesome', 'Awesome Nodes')
.version('1.0.0')
.node('fancy', 'Fancy Node', FancyNode, { icon: 'mdi:sparkles' })
.node('glow', 'Glowing Node', GlowNode, { icon: 'mdi:lightbulb' })
.register();
import {
unregisterFlowDropPlugin,
getRegisteredPlugins,
getPluginNodeCount,
isValidNamespace
} from '@flowdrop/flowdrop/editor';
unregisterFlowDropPlugin('awesome'); // Remove all nodes from a plugin
getRegisteredPlugins(); // List registered namespaces
getPluginNodeCount('awesome'); // Count nodes in a plugin
isValidNamespace('my-lib'); // Validate namespace format

Namespace rules: Must match /^[a-z][a-z0-9-]*$/ — lowercase letters, digits, and hyphens, starting with a letter.

If no custom component is registered for a type, FlowDrop falls back to built-in types:

TypeDescription
workflowNodeFull-featured node with inputs/outputs
simpleCompact layout
squareMinimal icon-only design
toolAgent tool nodes
gatewayBranching control flow
noteMarkdown sticky notes
terminalCircular start/end nodes

Use supportedTypes on NodeMetadata to let users switch between visual types at runtime.