Node Configuration & Schema-Driven Forms

Applies to: ChengOS v0.1.0+ | Last verified: 2026-08-12 | Source: chengflow-ui/src/features/editor/utils/controlUtils.tsx, chengflow-ui/src/features/editor/components/DynamicNodeInspector.tsx, chengflow/docs/i18n-node-convention-guide.md

Select a node and the right-hand property panel shows its configuration. Those forms are generated, not hand-written: the backend publishes each node's inputs as a JSON Schema carrying x-* UI extensions, and the frontend's field control registry turns each field into a control. Add an input to a node in Rust and its form field appears with no frontend change.

Knowing how that mapping works is what lets you predict the panel instead of being surprised by it.

How a field becomes a control

The registry resolves each field through an ordered chain and stops at the first match. The order is the important part:

  1. Connected state — highest priority. A field fed by an incoming edge renders as a read-only "connected" indicator, not an editable box. This is why a field you wired up stops being typeable: the edge is the value now.
  2. File path fields — render as a path picker.
  3. max_tokens — gets a purpose-built control.
  4. Sensitive fields — inferred from the field name. A name containing password, api_key/apikey, token/access_token, or secret/private_key becomes a masked input automatically. Passwords and secrets mask fully; API keys show the last 4 characters and tokens the last 6, so you can tell two keys apart without exposing either.
  5. Explicit x-control — the node author's declared choice (see the table below).
  6. Enum / options — a field with an enum becomes a select.
  7. Type match — the fallback: string → text, number → number, boolean → switch.

Step 4 is worth remembering when authoring nodes: naming a field api_key gets you masking for free, and naming a non-secret field token gets you masking you did not want.

Available controls

x-control selects from a fixed set of renderers:

ControlRenders as
text, input, textarea, rich-textSingle-line or expandable multi-line text
number, sliderNumeric entry or a slider
switch, checkboxToggle
select, radio, multi-selectChoice from options
code, code-editorSyntax-highlighted editor (language from x-language)
json, json-editorStructured JSON editor
key-valueEditable key/value pairs
date, time, datetime-localDate and time pickers
file, file-uploadPath picker or uploader (accepted types from x-accept)
color, color-pickerColour picker
credential-selectVault credential dropdown, filtered by x-credential-providers
llm-model-selectUnified model dropdown across all connected providers
workflow-selectWorkflow picker (filtered by x-workflow-types)
dynamic-selectOptions fetched at runtime from x-api-endpoint
mcp-server-manager, mcp-visibility-editorMCP server configuration widgets
document-extract-selectorDocument field selection widget

Extensions that change behaviour

Beyond picking a control, x-* extensions shape the panel itself:

Visibility and grouping

ExtensionEffect
x-advancedHidden behind the advanced section
x-conditionalShown only when a condition holds
x-depends-onOptions are recomputed from another field's value
x-collapsed-visibleStays visible on a minimized node
x-debug-fieldDiagnostic only
x-deprecatedMarked as deprecated but still rendered
x-readonlyDisplayed, not editable

Options sourcing

x-options supplies a static list. x-options-source / x-options-from supplies a dynamic one — notably ports, which builds options from the node's own input port names, which is how "pick one of my inputs" fields work. x-allow-custom lets you type a value that is not in the list.

Model selection

x-model-type, x-model-options, x-model-name-includes, x-model-name-excludes, and x-provider-filter narrow the model dropdown — so an embedding field offers embedding models rather than every model the provider exposes.

Other

x-rows (textarea height), x-step (numeric step), x-dynamic-array (repeatable groups), x-list-modal / x-compact-list / x-list-max-visible (list presentation), x-auto-populate (fill from context), x-fallback-control (what to use if the preferred control is unavailable), x-generic-kv and x-provider-presets (credential forms).

Referencing other nodes' values

Rather than hardcoding, reference an upstream value with {{variable_name}}. The variable selector — the picker beside supported fields — lists what is actually reachable at that point in the graph, which is more reliable than typing a name and hoping.

Two ways to get a value into a node, and the distinction matters:

Credentials

Fields with credential-select list matching vault entries by provider. Select an entry; the node stores a reference. The secret is fetched server-side at execution time and never sent to the browser or into a model prompt.

Translated labels

Labels, descriptions, placeholders, and option text come from i18n bundles keyed by x-i18n-key, loaded from /api/v1/i18n. Untranslated entries fall back to en-US rather than rendering blank, so a missing translation degrades instead of breaking.

Next steps