Skip to main content

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]
  • outputIndex selects a branch emitted on that port.
  • itemIndex selects 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:

NeedPath
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:

PurposeSyntax
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.