> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cyberdesk.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Structured Inputs

> Pass complex, nested data structures as workflow inputs instead of flattening into dozens of variables

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:

```text theme={null}
{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:

```text theme={null}
{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:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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' }
          ]
        }
      }
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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'}
                ]
            }
        }
    )
    ```
  </Tab>
</Tabs>

## Syntax Reference

### Dot Notation

Access object properties with `.`:

```text theme={null}
{user.email}
{config.settings.theme}
```

### Bracket Notation (Arrays)

Access array elements by index:

```text theme={null}
{customers[0]}
{items[2].name}
```

### Bracket Notation (Special Keys)

For keys with special characters, use bracket notation with quotes:

```text theme={null}
{data['special-key']}
{headers["Content-Type"]}
```

### Combining Notations

Chain any combination for deeply nested structures:

```text theme={null}
{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}}` |

<Note>
  Need these delimiters to appear literally instead of resolving as variables? Escape the opening delimiter:

  * `\{customer_name}` renders as literal `{customer_name}`
  * `\{{current_status}}` renders as literal `{{current_status}}`
  * `\{$support_pin}` renders as literal `{$support_pin}`

  This works in workflow prompt text and in input values you pass at run start.
</Note>

### Sensitive Inputs Example

```typescript theme={null}
// 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:

```text theme={null}
"Use focused_action to extract the invoice details and save them as {{invoice}}"
```

You can then access nested properties in subsequent steps:

```text theme={null}
"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](/concepts/sessions-and-chains), 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.

### Basic ref inside structured input

```typescript theme={null}
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' }
    }
  }
]
```

### Building structured inputs from multiple refs

You can mix refs with literal values anywhere in the structure:

```typescript theme={null}
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'
    }
  }
}
```

<Info>
  Cyberdesk keeps `$ref` values as references when you create the run, validates them against the destination input schema when possible, and resolves them at execution time before the receiving workflow uses them. Your workflow prompt receives the actual values, not the `$ref` syntax.
</Info>

<Tip>
  For more on refs and chains, see [Sessions and Chains](/concepts/sessions-and-chains#passing-data-between-steps-with-refs).
</Tip>

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

```text theme={null}
// 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):

```text theme={null}
// 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.

If you want to intentionally blank out a field that already has a value, use [`__CLEAR__`](/concepts/clear-values) instead of `__EMPTY__`.

### Array Out of Bounds

Accessing an array index that doesn't exist is treated as missing (not a type error):

```text theme={null}
// 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

```typescript theme={null}
// Prompt: {my_data}
// Input: {"my_data": {"a": 1, "b": 2}}
// Result: {"a": 1, "b": 2}  (stringified JSON)
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Group Related Fields" icon="layer-group">
    Organize related inputs into logical objects (e.g., `patient`, `billing`, `shipping`) rather than dozens of flat variables.
  </Card>

  <Card title="Use Consistent Structures" icon="arrows-repeat">
    Define a standard schema for your inputs across workflows. This makes SDK integration easier and reduces errors.
  </Card>

  <Card title="Validate Early" icon="check-double">
    Cyberdesk validates type mismatches at run start. Structure your inputs correctly to catch errors before execution begins.
  </Card>

  <Card title="Handle Optional Fields" icon="question">
    Missing nested fields become `__EMPTY__`. Design your prompts to handle this gracefully for optional data.
  </Card>
</CardGroup>

## Real-World Example: Healthcare Form

```text theme={null}
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}
```

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    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'
        }
      }
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    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'
            }
        }
    )
    ```
  </Tab>
</Tabs>

This approach transforms 15+ separate variables into a clean, hierarchical structure that mirrors your actual data model.
