# SimStudio AI Variable Resolution System and Block References Explained

> Understand SimStudio AI's variable resolution system and block references. Learn how {{blockId}} and <variableName> ensure precise data for block execution. Read the explanation now.

- Repository: [Sim/sim](https://github.com/simstudioai/sim)
- Tags: deep-dive
- Published: 2026-05-02

---

**SimStudio AI resolves workflow values in two distinct phases: block output references (`{{blockId.outputName}}`) are expanded first, then angle-bracket variables (`<variableName>`) are replaced, ensuring every block receives concrete data before execution begins.**

The `simstudioai/sim` repository implements a deterministic resolution engine that bridges workflow definitions with runtime execution. This article examines how the variable resolution system and block reference syntax function at the source code level, based on the type definitions in `packages/workflow-types` and the realtime handlers in `apps/realtime`.

## Variable Definition and Storage

Workflow variables are defined in the static workflow schema and persisted to the database before execution begins.

### Schema Definition

In [`packages/workflow-types/src/workflow.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-types/src/workflow.ts), the `Workflow` interface declares an optional `variables` map that stores workflow-wide values accessible to any block:

```typescript
export interface Workflow {
  // ...
  /** A map of workflow‑wide variables that can be referenced from any block. */
  variables?: Record<string, Variable>;
}

```

### Database Persistence

The variable map is stored as JSON in the database schema defined in [`packages/db/schema.ts`](https://github.com/simstudioai/sim/blob/main/packages/db/schema.ts):

```typescript
variables: json('variables')

```

### Runtime Loading

During execution, the realtime server loads these variables into memory. The handler in [`apps/realtime/src/handlers/variables.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/variables.ts) creates a plain JavaScript object attached to the execution context, making the values available to the resolution engine.

## Variable Resolution Syntax and Implementation

Blocks embed variables using the **angle-bracket** notation:

```

<myVariable>

```

### Resolution Algorithm

The resolution logic in [`apps/realtime/src/handlers/variables.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/variables.ts) scans input strings using the regex pattern `<([^>]+)>`. When a match is found, the engine looks up the captured name in the execution-time variables map:

```typescript
function resolveVariables(input: string, vars: Record<string, unknown>) {
  return input.replace(/<([^>]+)>/g, (_, name) => {
    if (Object.prototype.hasOwnProperty.call(vars, name)) {
      return String(vars[name]);
    }
    throw new Error(`Variable "${name}" not defined in workflow`);
  });
}

```

If a variable is missing, the engine throws a descriptive error before the block's core logic executes. This guarantees that API calls and tool invocations receive literal values rather than unresolved placeholders.

## Block Reference Resolution Mechanism

Blocks expose **named outputs** defined in their configuration, allowing downstream blocks to consume results through the **double-brace** syntax:

```

{{blockId.outputName}}

```

### Output Declaration

Block output schemas are defined in [`packages/workflow-types/src/blocks.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-types/src/blocks.ts). Each block specifies which fields it makes available to subsequent steps in the workflow graph.

### Runtime Resolution

The resolution routine resides in [`apps/realtime/src/handlers/subblocks.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/subblocks.ts). The engine performs four distinct steps:

1. **Token Identification**: Scan input strings for the pattern `{{blockId.outputName}}`
2. **Block Lookup**: Retrieve the source block from the current execution graph
3. **Output Retrieval**: Access the stored result map from completed blocks
4. **Value Substitution**: Replace the placeholder with the actual value

The implementation follows this structure:

```typescript
function resolveBlockRefs(
  input: string,
  executedBlocks: Record<string, BlockResult>
) {
  return input.replace(/{{([^}.]+)\.([^}]+)}}/g, (_, blockId, outputKey) => {
    const block = executedBlocks[blockId];
    if (!block) throw new Error(`Block "${blockId}" not executed`);
    if (!(outputKey in block.outputs))
      throw new Error(`Output "${outputKey}" not found on block "${blockId}"`);
    return String(block.outputs[outputKey]);
  });
}

```

If a referenced block has not yet run, the engine throws a **"dependency not satisfied"** error, enforcing correct execution order.

## Resolution Order and Nested References

The engine processes both resolution passes **before** a block's main logic executes. The specific order is:

1. **Block references** (`{{...}}`) are expanded first
2. **Variables** (`<...>`) are resolved in the resulting string

This sequencing enables nested usage patterns:

```

{{someBlock.output}}-<myVariable>

```

The resolver first expands the block reference to produce a concrete string, then replaces any remaining angle-bracket tokens. This allows variables to customize or postfix block outputs.

## Safety, Validation, and Auditing

### Schema Validation

Both variable definitions and block output schemas are typed with Zod in the contract layer (`apps/sim/lib/api/contracts/*`). This ensures the runtime never receives values of unexpected shapes.

### Error Handling

All resolution errors surface as `ZodError`-compatible messages. The API layer in `apps/sim/lib/api/server` formats these into 400 responses with descriptive explanations of which variable or block reference failed.

### Audit Logging

Variable updates are logged via the audit system in [`packages/audit/src/log.ts`](https://github.com/simstudioai/sim/blob/main/packages/audit/src/log.ts) using the `WORKFLOW_VARIABLES_UPDATED` action. This creates a permanent record of who modified workflow variables and when the changes occurred.

## Code Examples

### Referencing a Workflow Variable

```json
{
  "variables": {
    "apiKey": { "type": "string", "value": "ABCD1234" }
  },
  "blocks": [
    {
      "id": "callStripe",
      "type": "stripe/charge",
      "params": {
        "apiKey": "<apiKey>",
        "amount": "5000"
      }
    }
  ]
}

```

During execution, the `callStripe` block receives the literal string `"ABCD1234"` for its `apiKey` parameter.

### Chaining Block Outputs

```json
{
  "blocks": [
    {
      "id": "fetchUser",
      "type": "http/get",
      "params": { "url": "https://api.example.com/user/123" },
      "outputs": { "responseBody": "string" }
    },
    {
      "id": "parseUser",
      "type": "json/parse",
      "params": {
        "json": "{{fetchUser.responseBody}}"
      }
    }
  ]
}

```

The `parseUser` block receives the raw HTTP response body that `fetchUser` stored under `responseBody`.

### Combining Both Resolution Types

```json
{
  "variables": { "suffix": { "type": "string", "value": "_v2" } },
  "blocks": [
    {
      "id": "makeName",
      "type": "string/concat",
      "params": { "parts": ["base", "<suffix>"] }
    },
    {
      "id": "final",
      "type": "log/print",
      "params": { "message": "{{makeName.result}}" }
    }
  ]
}

```

1. `makeName` resolves `<suffix>` to `"_v2"` and concatenates to produce `"base_v2"`
2. `final` prints `"base_v2"` by referencing `{{makeName.result}}`

## Summary

- **Variable storage**: Defined in [`packages/workflow-types/src/workflow.ts`](https://github.com/simstudioai/sim/blob/main/packages/workflow-types/src/workflow.ts), persisted in [`packages/db/schema.ts`](https://github.com/simstudioai/sim/blob/main/packages/db/schema.ts), and loaded by [`apps/realtime/src/handlers/variables.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/variables.ts)
- **Variable syntax**: Angle brackets (`<variableName>`) resolved via regex in the variables handler
- **Block reference syntax**: Double braces (`{{blockId.outputName}}`) processed by [`apps/realtime/src/handlers/subblocks.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/subblocks.ts)
- **Execution order**: Block references resolve first, then variables, enabling nested composition
- **Safety**: Zod schema validation, descriptive error messages, and audit logging via [`packages/audit/src/log.ts`](https://github.com/simstudioai/sim/blob/main/packages/audit/src/log.ts)

## Frequently Asked Questions

### What is the difference between angle brackets and double braces in SimStudio AI workflows?

Angle brackets (`<variableName>`) reference **workflow variables** stored in the execution context, while double braces (`{{blockId.outputName}}`) reference **block outputs** from previous steps in the workflow graph. Variables are defined statically in the workflow schema, whereas block references are dynamic values produced during execution.

### How does the simstudioai/sim engine handle missing references?

The engine throws descriptive errors before block execution begins. If a variable is missing, the resolver in [`apps/realtime/src/handlers/variables.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/variables.ts) throws `Variable "${name}" not defined in workflow`. If a block reference points to an unexecuted block or missing output, [`apps/realtime/src/handlers/subblocks.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/subblocks.ts) throws either `Block "${blockId}" not executed` or `Output "${outputKey}" not found`. These errors propagate as 400 responses with precise location information.

### Can I nest block references inside variables or vice versa?

Yes. The resolution system supports nested usage because it processes block references first, then variables. For example, `{{fetchData.url}}-<environment>` first expands the block reference to a URL string, then appends the environment variable suffix. However, you cannot reference a variable inside a block ID or output name—the `{{...}}` syntax requires literal block identifiers.

### Where does simstudioai/sim store workflow variables during execution?

Variables are stored in three locations: (1) the database JSON field defined in [`packages/db/schema.ts`](https://github.com/simstudioai/sim/blob/main/packages/db/schema.ts) for persistence, (2) the in-memory execution context created by [`apps/realtime/src/handlers/variables.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/handlers/variables.ts) during runtime, and (3) the audit logs in [`packages/audit/src/log.ts`](https://github.com/simstudioai/sim/blob/main/packages/audit/src/log.ts) via the `WORKFLOW_VARIABLES_UPDATED` action for change tracking. The runtime copy is a plain JavaScript object consulted by the resolution regex before each block executes.