Surface data references
A surface binding is an object with a path:
{
path: '$output/Get Second Resource/main/0/0/body/uuid',
default: 'Not available',
format: 'text',
}
The platform validates the first path segment. Data bindings can start with $output, $input, or
$binary. Every remaining segment is literal and is resolved by the UI exactly as written.
default is used when the path cannot be resolved. format can be text, number, date,
datetime, or json.
Data model shape
Each rendered node receives its own data model:
type DataModel = {
$output: {
[stepName: string]: {
[port: string]: unknown[][];
};
};
$input: {
[stepName: string]: {
[port: string]: unknown[][];
};
};
$binary: {
[stepName: string]: {
[port: string]: Array<Array<Record<string, BinaryDescriptor>>>;
};
};
};
Keys under each root are workflow step names, not step IDs.
The two arrays under a port have different meanings:
port[outputIndex][itemIndex]
outputIndexselects a branch emitted on that port.itemIndexselects one item within that branch.
Reference the attached step's output
$output contains the output of the step whose surface is being rendered:
{
path: '$output/Get Second Resource/main/0/0/statusMessage',
}
General form:
$output/<step name>/<port>/<output index>/<item index>/<JSON path...>
Examples:
| Need | Path |
|---|---|
| Whole projected output root | $output |
| First JSON item | $output/Get Second Resource/main/0/0 |
| Nested field | $output/Get Second Resource/main/0/0/body/uuid |
| Second emitted item | $output/Get Second Resource/main/0/1/body/uuid |
| Output branch 1 | $output/Check Condition/main/1/0/reason |
Reference an upstream step
$input contains executed upstream steps that contributed to the current node's lineage. Use it to
show another step's output in the current surface:
{
path: '$input/Support Agent/main/0/0/response',
}
For a sequential workflow Load Ticket → Support Agent → Save Result, a surface on Save Result can
read both upstream steps:
const resultSurface = new Surface({
id: 'support-result',
name: 'Support result',
slot: 'output',
definition: {
schema: 'kit.ui/v1',
root: 'layout',
components: [
{
id: 'layout',
type: 'Stack',
children: ['subject', 'reply'],
},
{
id: 'subject',
type: 'Text',
props: {
value: {
path: '$input/Load Ticket/main/0/0/subject',
},
},
},
{
id: 'reply',
type: 'Markdown',
props: {
value: {
path: '$input/Support Agent/main/0/0/response',
},
},
},
],
},
});
On a conditional workflow, only the branch that actually executed and contributed to the current
node is present. Do not assume that an unexecuted branch exists in $input.
Reference nested arrays
Segments after the item index traverse the JSON value. Numeric segments select array elements:
// First slide in the first output item.
{
path: '$output/Get Slides/main/0/0/slides/0/title',
}
// Second tag on the third customer in an upstream item.
{
path: '$input/Load Customers/main/0/0/customers/2/tags/1',
}
The path is not rewritten. If you omit main, an output index, or an item index, the UI does not
insert it for you.
Reference binary output
$binary contains descriptors for binary files emitted by the attached step. It does not contain
raw base64 data:
{
path: '$binary/Binary Agent/main/0/0/processedSummary',
}
General form:
$binary/<step name>/<port>/<output index>/<item index>/<binary key>
The resolved descriptor has this shape:
type BinaryDescriptor = {
key: string;
fileName?: string;
mimeType?: string;
size?: number;
downloadUrl: string;
};
Bind the descriptor to a FileChip or another file-aware component:
{
id: 'file',
type: 'FileChip',
props: {
file: {
path: '$binary/Binary Agent/main/0/0/processedSummary',
},
},
}
The UI uses downloadUrl to fetch the file. Execution and surface responses never include the raw
binary bytes.
Other ports
The path grammar supports any port name:
{
path: '$output/Tool Provider/ai_tool/0/0/name',
}
Current surface projection populates only main. Other port names are reserved for nodes that
expose those values in a future surface data model. A path using an unpopulated port resolves to its
default, or causes the UI to try the next surface if rendering cannot continue.
Form state is separate
Wait surfaces use $form for values entered by the user:
props: {
bind: '/$form/comment',
}
actions: {
approve: {
type: 'node.resume',
payload: {
comment: { path: '$form/comment', default: '' },
},
},
}
$form is available only to wait-surface form bindings and actions. It is not part of the execution
dataModel. $exec is not a supported surface root.
Surface paths versus workflow expressions
Surface paths and workflow expressions solve related but different problems:
| Purpose | Syntax |
|---|---|
| Pass Support Agent output into another workflow step | ={{ $('Support Agent').json.response }} |
| Render Support Agent output in a downstream surface | $input/Support Agent/main/0/0/response |
| Render the attached step's output | $output/Save Result/main/0/0/response |
See Workflow data and expressions for workflow parameter syntax.