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]: {
main: unknown[][];
};
};
$input: {
[stepName: string]: {
main: unknown[][];
};
};
$binary: {
[stepName: string]: {
main: Array<Array<Record<string, BinaryDescriptor>>>;
};
};
};

Keys under each root are workflow step names, not step IDs. main is the only port; every step's data lands there.

The two arrays under main have different meanings:

main[outputIndex][itemIndex]
  • outputIndex selects a branch emitted on main.
  • 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>/main/<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>/main/<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.

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.