NodeHandlerInterface::configSchema(). The Vue builder’s
generic renderer (SchemaConfigRenderer.vue → SchemaFields.vue) reads this shape directly — when a node type has no
inline-styled override (current overrides: SendMessage / Input / Condition / Branch / Assign / Call — see OVERRIDES
in ConfigPanel.vue), the entire right-pane config form is built from this array. (subflow and end are now fully
schema-driven via the flow-picker and enum-cards field types.)
Keep this file in sync with resources/js/builder/components/editor/config/SchemaFields.vue#FIELD_COMPONENTS. If you
add a new field type to the renderer, extend the matching section here in the same PR.
1. Overview
Two equivalent ways to express a schema — the fluent builder fromfapost/support (recommended) or the raw array. The
renderer reads the same wire shape in both cases.
Fluent (preferred):
make(string $name) factory (TextField::make(),
NumberField::make(), …) — there is no Field::string() style facade. Concrete field classes live under the
Fields\ sub-namespace; Schema and Section live directly under …\Builder\Schema.
Raw array (still supported):
resources/js/builder/components/editor/config/. There is no separate JSON-schema validator —
the renderer is permissive and falls back to TextField for unknown types, the backend validator
(FlowDefinitionValidator) is the contract for runtime correctness.
2. Top-level keys
Keys that are not arrays-with-a-
type, not required, and not sections are ignored.
2.1 sections
- Sections are rendered in array order. Field keys not declared in any section are dropped (intentional, so authors see visually where each field lives).
- If
sectionsis missing entirely, the renderer falls back to a single auto-section labelled “Configuration” containing every declared field. - A
Metasection with the node id is always appended by the renderer — handlers don’t declare it.
2.2 Section icons
Currently registered Heroicon names (seeresources/js/builder/components/editor/config/sectionIcons.ts):
globe-alt, arrow-down-tray, cog-6-tooth, book-open, magnifying-glass, adjustments-horizontal, bolt,
cube, arrow-right-circle, play, clock, flag, square-3-stack-3d.
Unknown names fall back to a neutral square — extend sectionIcons.ts rather than passing arbitrary SVG.
3. Field types
Common props (apply to every type unless noted): see §4. Conditional visibility: see §5. Inline validators: see §6.3.1 string
Single-line text input. Renders TextField.vue with VariablePicker for {{path}} insertion.
3.2 text
Multi-line textarea. Renders TextareaField.vue with VariablePicker.
Same prop set as string plus a default of 4 rows of height. No regex validator at the moment.
3.3 number
Numeric input. Renders TextField.vue with type="number" and no VariablePicker (templates don’t substitute into
numeric fields).
3.4 boolean
Checkbox toggle. Renders ToggleField.vue.
3.5 enum
Dropdown select. Renders SelectField.vue.
3.6 array
List of strings. Renders ArrayField.vue — one input per item, + Add item button, per-row delete.
3.7 json
Raw JSON textarea. Renders JsonField.vue with Victor Mono font and inline parse-error display. Value is the parsed
object/array, not the raw string.
3.8 state-picker
Flow-state path input. Renders StatePickerField.vue — monospace input with VariablePicker.
3.9 key-value
Record<string, string> editor — paired inputs, + Add button. Renders KeyValueField.vue. Duplicate keys are flagged
inline (last-write-wins on commit). Empty keys are dropped on commit.
3.10 object
Nested group of fields under a single config key. Renders ObjectField.vue — recursively delegates to SchemaFields
with the sub-schema. State path is config[key].subkey; the parent emits the full sub-object on every change, no deep
merge in the store.
visible_when paths inside an object resolve against the root config — use dot-notation
(transport_options.retries) to reach nested values from sibling fields.
3.11 object-array
Repeater of structured objects. Renders ObjectArrayField.vue — each item is a collapsible accordion with
SchemaFields for item.fields. Authors get + Add item, per-item delete, and (optional) min/max bounds.
The
item_label template substitutes {key} for top-level item values — handy for the collapsed-row preview.
3.12 flow-picker
Searchable dropdown over the assistant’s flows. Renders FlowPickerField.vue (on top of SearchableSelect.vue).
Stores the selected flow.id (UUID, language-agnostic), never the name. The option list is read from the builder
runtime store (builderStore.availableFlows), not from the schema — so no options key is declared.
subflow.flow_id. exclude_current prevents a flow from referencing itself.
3.13 enum-cards
Radio-card enum — a richer alternative to enum (§3.5) where each option is a full-width clickable card with an
optional icon, hint line and colour accent. Renders EnumCardsField.vue. Stores the selected option’s value.
accent tints the selected card. Known accents: sage (green), amber, rose; unknown / omitted falls back to the
neutral primary accent.
end.status.
4. Common field props
Available on every field declaration.5. Conditional visibility — visible_when
A field can declare a condition that’s evaluated against the root config; when it fails, the field is not rendered.
The stored value stays in node.config even while hidden — flipping the condition back restores the data without
loss. Runtime behaviour for hidden fields is the handler’s responsibility (typically: ignore unless parent toggle is
on).
Short form (equality, AND of all clauses):
equals (default), in (value must be an array, actual must be one of), truthy.
Dot-paths reach into nested objects: transport_options.retries, headers.Authorization. Paths that don’t resolve
evaluate as undefined (so equals only matches if value itself is undefined).
6. Field-level validators
Inline-only — backendFlowDefinitionValidator remains the source of truth for what blocks Publish.
Validators do not block saves — they’re a UX cue so authors fix obvious typos before hitting Publish. Anything
load-bearing must live in the backend validator.
7. Reserved top-level keys
These keys can’t be used as field names — they’re consumed by the renderer for other purposes:required— top-levelarray<string>of required field keys.sections— top-level grouping.
tests/Unit/Domains/Flow/NodeHandlerSchemaSectionsTest.php) enforces that sections[*].fields only
references declared field keys.
8. Migration notes
- Pre-Phase-1 schemas (flat field list, no
sections) still render — the fallback “Configuration” section catches every declared field. No change required unless you want the new visual grouping. switchfield type — removed. Usecondition(branch handler) with multiple rules instead.save_to(string) on SendMessage / Input — superseded bysave_to_variable/variableobjects (Variableshape:{ name, type, storage, group }). Legacysave_tostill accepted by the runtime; new authors should use the new shape.
9. Fluent builders (fapost/support)
The wire format from §2–§3 is generated by the Fapost\Support\Builder\Schema namespace. It’s a thin layer over the
same shape — toArray() is the only contract — giving authors autocomplete, type-safe field-specific methods (only
min()/max() on NumberField, only regex() on TextField, etc.) and refactor-safety on a vocabulary that’s
growing. Every Core handler is fluent today, but the renderer reads the wire shape, so a raw array (e.g. from a Plugin
that doesn’t depend on fapost/support) renders identically.
Each field is a concrete class under Fapost\Support\Builder\Schema\Fields\* with a static make(string $name)
factory inherited from the abstract Field. Schema and Section live directly under
Fapost\Support\Builder\Schema.
Common methods on every field:
label(), help(), default(), placeholder(), required(bool = true),
visibleWhen(array).