When automating form-heavy workflows, you often need to pass many related fields. Instead of creating a separate variable for each field, you can pass structured JSON objects and access nested properties directly in your prompts.
The Problem
Consider a loan document with a “Section G” containing many fields. Without structured inputs, you’d need to define separate variables for each:
{section_g_cost}
{section_g_account_number}
{section_g_customers_1_id}
{section_g_customers_1_name}
{section_g_customers_1_description}
... (many more)
This becomes unwieldy and error-prone.
The Solution: Nested Access
With structured inputs, you can pass a single JSON object and access nested properties directly:
{section_g.cost}
{section_g.account_number}
{section_g.customers[0].id}
{section_g.customers[0].name}
{section_g.customers[1].description}
Then when creating a run, you simply pass:
const { data : run } = await client . runs . create ({
workflow_id: 'workflow-uuid' ,
input_values: {
section_g: {
cost: 50000 ,
account_number: 'ACC-12345' ,
customers: [
{ id: 'C001' , name: 'John Doe' , description: 'Primary applicant' },
{ id: 'C002' , name: 'Jane Doe' , description: 'Co-applicant' }
]
}
}
});
run_data = RunCreate(
workflow_id = 'workflow-uuid' ,
input_values = {
'section_g' : {
'cost' : 50000 ,
'account_number' : 'ACC-12345' ,
'customers' : [
{ 'id' : 'C001' , 'name' : 'John Doe' , 'description' : 'Primary applicant' },
{ 'id' : 'C002' , 'name' : 'Jane Doe' , 'description' : 'Co-applicant' }
]
}
}
)
Syntax Reference
Dot Notation
Access object properties with .:
{user.email}
{config.settings.theme}
Bracket Notation (Arrays)
Access array elements by index:
{customers[0]}
{items[2].name}
Bracket Notation (Special Keys)
For keys with special characters, use bracket notation with quotes:
{data['special-key']}
{headers["Content-Type"]}
Combining Notations
Chain any combination for deeply nested structures:
{form.sections[0].fields['field-name'].value}
{response.data.users[0].profile.settings['notification-preferences']}
Works Across All Variable Types
Structured access works consistently for all three variable types:
Type Syntax Example Input {var.path}{patient.demographics.dob}Sensitive {$var.path}{$credentials.api_key}Runtime {{var.path}}{{extracted_data.invoice_id}}
// Workflow prompt uses: {$credentials.username} and {$credentials.password}
const { data : run } = await client . runs . create ({
workflow_id: 'workflow-uuid' ,
sensitive_input_values: {
credentials: {
username: 'admin' ,
password: 's3cr3t'
}
}
});
Runtime Values Example
When a focused_action sets a runtime value as an object:
"Use focused_action to extract the invoice details and save them as {{invoice}}"
You can then access nested properties in subsequent steps:
"Enter the invoice number {{invoice.number}} in the search field"
"Verify the amount matches {{invoice.line_items[0].amount}}"
Using Refs in Chains
When running multiple workflows in a session chain , you can use $ref to pass outputs from previous steps as inputs to later steps. Refs work inside structured inputs , allowing you to build complex input objects from prior outputs.
steps : [
{
workflow_id: 'extract-workflow' ,
session_alias: 'extract' ,
inputs: { document_id: '12345' }
// This step outputs: { result: { customer: { name: 'John', email: 'john@example.com' } } }
},
{
workflow_id: 'process-workflow' ,
session_alias: 'process' ,
inputs: {
// Use the entire extracted object
customer_data: { $ref: 'extract.outputs.result.customer' }
}
}
]
You can mix refs with literal values anywhere in the structure:
inputs : {
data : {
// Ref to a nested field
query : { $ref : 'step1.outputs.result.query' },
// Ref to an array element
first_keyword : { $ref : 'step1.outputs.result.keywords[0]' },
context : {
// Another ref
summary : { $ref : 'step1.outputs.result.summary' },
// Literal value mixed in
priority : 'high'
}
}
}
Refs are resolved server-side before the workflow runs. Your workflow prompt receives the actual values, not the $ref syntax.
Error Handling
Type Mismatches (Fails the Run)
If you try to access a nested property on a value that isn’t an object or array, the run fails immediately with a clear error:
// Prompt: {section_g.cost}
// Input: {"section_g": "just a string"}
// ❌ Error: Cannot access path '.cost' on input_values['section_g'] because it is a str, not an object or array
This catches configuration errors early, saving you time and usage costs.
Missing Fields (Uses __EMPTY__)
If a nested path simply doesn’t exist, it’s replaced with the __EMPTY__ sentinel value (same behavior as regular empty inputs):
// Prompt: {section_g.optional_field}
// Input: {"section_g": {"cost": 500}}
// Result: __EMPTY__ (field doesn't exist)
This allows workflows to gracefully handle optional nested fields.
Array Out of Bounds
Accessing an array index that doesn’t exist is treated as missing (not a type error):
// Prompt: {customers[5].name}
// Input: {"customers": [{"name": "John"}]}
// Result: __EMPTY__ (only 1 item in array)
Backward Compatibility
Existing workflows continue to work exactly as before:
{variable} with a string value → replaced with the string
{variable} with an object/array value (no nested access) → JSON stringified
// Prompt: {my_data}
// Input: {"my_data": {"a": 1, "b": 2}}
// Result: {"a": 1, "b": 2} (stringified JSON)
Best Practices
Group Related Fields Organize related inputs into logical objects (e.g., patient, billing, shipping) rather than dozens of flat variables.
Use Consistent Structures Define a standard schema for your inputs across workflows. This makes SDK integration easier and reduces errors.
Validate Early Cyberdesk validates type mismatches at run start. Structure your inputs correctly to catch errors before execution begins.
Handle Optional Fields Missing nested fields become __EMPTY__. Design your prompts to handle this gracefully for optional data.
Fill out the patient intake form:
**Demographics**
- First Name: {patient.demographics.first_name}
- Last Name: {patient.demographics.last_name}
- DOB: {patient.demographics.date_of_birth}
- SSN: {$patient.ssn}
**Insurance**
- Provider: {patient.insurance.provider}
- Policy Number: {patient.insurance.policy_number}
- Group ID: {patient.insurance.group_id}
**Emergency Contacts**
- Primary Contact: {patient.emergency_contacts[0].name}
- Primary Phone: {patient.emergency_contacts[0].phone}
- Secondary Contact: {patient.emergency_contacts[1].name}
- Secondary Phone: {patient.emergency_contacts[1].phone}
const { data : run } = await client . runs . create ({
workflow_id: 'patient-intake-workflow' ,
input_values: {
patient: {
demographics: {
first_name: 'John' ,
last_name: 'Doe' ,
date_of_birth: '1985-03-15'
},
insurance: {
provider: 'Blue Cross' ,
policy_number: 'BC123456' ,
group_id: 'GRP001'
},
emergency_contacts: [
{ name: 'Jane Doe' , phone: '555-0101' },
{ name: 'Bob Smith' , phone: '555-0102' }
]
}
},
sensitive_input_values: {
patient: {
ssn: '123-45-6789'
}
}
});
run_data = RunCreate(
workflow_id = 'patient-intake-workflow' ,
input_values = {
'patient' : {
'demographics' : {
'first_name' : 'John' ,
'last_name' : 'Doe' ,
'date_of_birth' : '1985-03-15'
},
'insurance' : {
'provider' : 'Blue Cross' ,
'policy_number' : 'BC123456' ,
'group_id' : 'GRP001'
},
'emergency_contacts' : [
{ 'name' : 'Jane Doe' , 'phone' : '555-0101' },
{ 'name' : 'Bob Smith' , 'phone' : '555-0102' }
]
}
},
sensitive_input_values = {
'patient' : {
'ssn' : '123-45-6789'
}
}
)
This approach transforms 15+ separate variables into a clean, hierarchical structure that mirrors your actual data model.