config.yaml is the settings form. Canvas renders it, someone fills it in, and your
calls read the saved values as {{ config.<field> }}.
It is the file you touch most. Every new option a node grows lands here and nowhere else.
config.yaml
configSchema is a JSON Schema, so any keyword you already know works. The ui: keys are
the platformβs, and they decide how a field is drawn.
Writing the labels
Three fields decide whether the form makes sense to the person filling it in.
Say what the setting does, not what it is called. βMaximum number of tokens to generateβ
tells a reader nothing they could not get from the label. βThe model stops when it hits this,
mid-sentence and without an errorβ tells them why they might change it.
Keep it to a line or two. Detail belongs in your nodeβs own documentation, not under a form
field.
Field types
Text
A choice
enum is the values, enumNames is what a person sees. They are positional, so they must be
the same length.
A number
minimum and maximum are enforced, so a bad value is caught in the form rather than by the
service.
A switch
Structured data
Making a field wirable
ui:field: template is what lets a field take data from an upstream node instead of a typed
value. Without it, the field is whatever someone typed.
The syntax is decided by the fieldβs type, always, on every node.
A string field takes a Handlebars template.
There is no
input.* root. A path that matches nothing resolves to empty and says
nothing about it, so check the id against the edge you actually drew. Array elements and
object keys are dot segments, never brackets: records.0.Name, not records[0].Name.
Helpers work anywhere a template does: eq, contains, filter and toJSON. So a
conditional lives in the field itself.
object or array field takes a return expression instead.
{ topic: "{{...}}" }, is not valid and never resolves.
The running workflow
The five roots above are your nodeβs own surroundings. A config field can also reach the run it is part of.workflow.variables is the one to reach for when several nodes need the same value. Set it
once on the workflow rather than wiring it into each node.
These are resolved before your node runs, when the platform prepares its settings. So
they are available in config.yaml fields, and your calls read the result as
{{ config.<field> }}.
What a return expression may do
It is not JavaScript. It is a data-shaping expression that reshapes what is already in front
of it, and nothing else.
Allowed: reading the context, indexing, object and array literals, spread, template
strings, operators, ternaries, and arrow callbacks like items.map(x => x.name).
Methods: the non-mutating array ones (
map, filter, slice, find, reduce, flat,
at, toSorted), the string ones (split, replace, trim, padStart, match), and
toFixed.
Rejected: process, require, fetch, globalThis, eval, Function, new,
assignment, statement blocks, constructor and __proto__. A rejected expression is logged
and never runs.
Nothing mutates. Use .at(-1) rather than .pop(), and .toSorted() rather than
.sort(). sort reorders the array in place, and that array is a live upstream output, so
sorting it would reorder it for every other node reading the same value.
Dates
Half the APIs a node calls take a date range, and every one of them wantsYYYY-MM-DD or a
full ISO string. Date.iso is the formatter.
Date.now() is milliseconds, Date.iso(ms) gives the full ISO string, and .split('T')[0]
gives the date part. One function rather than a family, and there is no new Date(...)
because that is a construction the sandbox refuses.
Security here is by absence. The interpreter never implements those globals, so there is
nothing for an expression to escape to.
Showing a field only when it matters
ui:dependencies hides a field until another field has the right value. A scalar means it
must match exactly, an array means it must be one of several, and multiple keys are all
required at once.
The two fields every node has
Two of them are not yours to choose. They are identical in every node, and lint checks they are there:node.yaml, and the stricter of the two wins.
Both default to off, because a node that gated by default would break every workflow already
using it. Who Can Run It covers the whole model.
The rest of the vocabulary
When it goes wrong
Next: Service Connectors

