Skip to content

Configuration Forms

FlowDrop automatically generates configuration forms from JSON Schema definitions. This guide covers static schemas, dynamic runtime forms, and layout control.

FlowDrop provides three ways to define configuration forms for nodes:

ApproachWhen to use
Static configSchemaFields are known ahead of time
Dynamic configEdit.dynamicSchemaFields depend on external data or user selections
External configEdit.externalEditLinkConfiguration is managed by a 3rd-party system

All three approaches can be combined — FlowDrop tries the dynamic schema first, then falls back to the static schema if the fetch fails.

Define configSchema on your node metadata. FlowDrop auto-renders the form:

const myNode: NodeMetadata = {
id: 'my-processor',
name: 'My Processor',
description: 'Processes data',
category: 'processing',
version: '1.0.0',
inputs: [{ id: 'in', name: 'Input', dataType: 'any' }],
outputs: [{ id: 'out', name: 'Output', dataType: 'any' }],
configSchema: {
type: 'object',
properties: {
name: {
type: 'string',
title: 'Name',
description: 'A friendly name for this processor'
},
enabled: {
type: 'boolean',
title: 'Enabled',
default: true
}
},
required: ['name']
},
config: { enabled: true }
};
typeRenders as
stringText input
numberNumber input
integerInteger input
booleanToggle switch
arrayRepeatable field list
objectNested fieldset

Use format to change how a field renders:

FormatRenders asNotes
multilineTextareaMulti-line text input
hiddenNothingStored in config but not shown in UI
rangeSliderRequires minimum and maximum
jsonCodeMirror editorJSON syntax highlighting and validation
codeCodeMirror editorAlias for json
markdownMarkdown editorToolbar and preview
templateCodeMirror editor{{ variable }} autocomplete
autocompleteText input + suggestionsFetches options from API
{
"type": "object",
"properties": {
"prompt": {
"type": "string",
"title": "Prompt",
"format": "multiline"
},
"temperature": {
"type": "number",
"title": "Temperature",
"format": "range",
"minimum": 0,
"maximum": 2,
"default": 0.7
},
"metadata": {
"type": "object",
"title": "Metadata",
"format": "json"
},
"internalId": {
"type": "string",
"format": "hidden"
}
}
}
{
"model": {
"type": "string",
"title": "Model",
"enum": ["gpt-4o", "gpt-4o-mini", "claude-3"],
"default": "gpt-4o-mini"
}
}
{
"status": {
"type": "string",
"title": "Status",
"oneOf": [
{ "const": "pending", "title": "Pending" },
{ "const": "in_progress", "title": "In Progress" },
{ "const": "completed", "title": "Completed" }
]
}
}
{
"tags": {
"type": "string",
"title": "Tags",
"enum": ["urgent", "review", "archive"],
"multiple": true
}
}

Fetch suggestions from a remote API as the user types:

{
"userId": {
"type": "string",
"title": "User",
"format": "autocomplete",
"autocomplete": {
"url": "/api/users/search",
"queryParam": "q",
"minChars": 2,
"debounceMs": 300,
"labelField": "name",
"valueField": "id",
"allowFreeText": false,
"fetchOnFocus": true
}
}
}

Template fields provide CodeMirror editing with {{ variable }} syntax highlighting and autocomplete from connected node outputs:

{
"prompt": {
"type": "string",
"title": "Prompt Template",
"format": "template",
"variables": {
"ports": ["data", "context"],
"showHints": true
}
}
}

By default, fields render in property order. Use uiSchema to control layout and grouping — inspired by JSON Forms:

{
"type": "VerticalLayout",
"elements": [
{ "type": "Control", "scope": "#/properties/name" },
{ "type": "Control", "scope": "#/properties/model" },
{
"type": "Group",
"label": "Advanced Settings",
"collapsible": true,
"defaultOpen": false,
"elements": [
{ "type": "Control", "scope": "#/properties/temperature" },
{ "type": "Control", "scope": "#/properties/maxTokens" }
]
}
]
}
TypeDescription
ControlRenders a single form field. scope is a JSON Pointer to the property.
VerticalLayoutStacks child elements vertically.
GroupWraps elements in a collapsible fieldset with a label.

Certain property names trigger automatic behaviors:

PropertyTypeBehavior
instanceTitlestringOverrides the node’s displayed title
instanceDescriptionstringOverrides the node’s displayed description
instanceBadgestringOverrides the node’s badge
nodeTypestringSwitches visual rendering type
dynamicInputsDynamicPort[]Creates user-defined input handles
dynamicOutputsDynamicPort[]Creates user-defined output handles
branchesBranch[]Creates conditional output paths (gateway nodes)

Use configEdit.dynamicSchema to fetch config schemas from your backend at runtime:

const myNode: NodeMetadata = {
id: 'dynamic-processor',
name: 'Dynamic Processor',
configEdit: {
dynamicSchema: {
url: '/api/nodes/{nodeTypeId}/schema',
method: 'GET',
parameterMapping: {
nodeTypeId: 'metadata.id'
},
cacheSchema: true,
timeout: 10000
},
showRefreshButton: true
},
// Fallback static schema
configSchema: {
type: 'object',
properties: {
apiKey: { type: 'string', title: 'API Key' }
}
}
};

The dynamic schema endpoint should return a JSON Schema object. Multiple response shapes are accepted:

  • Direct schema: { type: "object", properties: {...} }
  • Wrapped: { data: {...} } or { schema: {...} }
  • With UISchema: { configSchema: {...}, uiSchema: {...} }

For configuration managed by a 3rd-party system:

configEdit: {
externalEditLink: {
url: 'https://admin.example.com/nodes/{nodeTypeId}/configure',
label: 'Configure in Admin Portal',
icon: 'mdi:open-in-new',
parameterMapping: {
nodeTypeId: 'metadata.id'
},
openInNewTab: true
}
}

You can render the ConfigForm component independently:

<script>
import { ConfigForm } from '@flowdrop/flowdrop';
const schema = {
type: 'object',
properties: {
name: { type: 'string', title: 'Name' },
email: { type: 'string', title: 'Email' }
},
required: ['name', 'email']
};
let values = $state({});
</script>
<ConfigForm
{schema}
{values}
onChange={(config) => {
values = config;
}}
onSave={(config) => {
/* persist */
}}
/>