Reading Upstream Output
Each node's output data is stored in the execution's ExecutionMemory keyed by node ID. Downstream executors read this data via expression references ($output.{nodeId}.fieldName) that are resolved at configuration parse time, or directly from the memory object at runtime.
How Data Flows
When a node returns a NodeExecutionResult with OutputData, the engine stores that dictionary in ExecutionMemory.nodeOutputs[nodeId]. The next node's configuration expressions (e.g. $output.http-request-1.body) are resolved against this memory before the config is passed to the executor.
Expression Resolution (Most Common Pattern)
In most cases, upstream data is already resolved into your node's configuration by the time ExecuteInternalAsync runs. The user writes an expression like $output.http-request-1.statusCode in the Atlas Form field, and the engine substitutes the actual value before calling your executor. You read it as a normal config key:
// Node config JSON after expression resolution:
// { "recipientEmail": "alice@example.com" } ← resolved from $output.form-1.email
// In settings class:
public string RecipientEmail => ReadConfigByKey("recipientEmail", string.Empty)!;
// In executor — just reads the already-resolved value
var recipient = mySettings!.RecipientEmail; // "alice@example.com"
Direct Memory Access (Advanced)
For cases where you need to read upstream output programmatically at runtime (e.g. iterating a collection produced by a previous node), access ExecutionMemory directly:
protected override async Task<NodeExecutionResult> ExecuteInternalAsync(
NodeExecutionContext ctx,
CancellationToken ct)
{
var elCtx = ctx.ElementExecutionContext!;
var memory = elCtx.ExecutionMemory;
// Get the ID of the previous node (from the definition)
var prevNodeId = elCtx.Definition.PreviousNodeId;
// Get the output dictionary from that node
if (memory.nodeOutputs.TryGetValue(prevNodeId, out var prevOutput))
{
var items = prevOutput["items"] as List<object>;
// ... process items ...
}
}
ExecutionMemory Keys
| Key | Type | Contents |
|---|---|---|
nodeOutputs | Dictionary<string, Dictionary<string, object>> | Output data keyed by node ID. All completed nodes. |
variables | Dictionary<string, object> | Workflow-scoped variables set by Variable Assignment nodes |
globalData | Dictionary<string, object> | Execution-wide data (trigger payload, initial input) |
Expression Syntax Reference
| Expression | Resolves To |
|---|---|
$output.{nodeId}.{field} | Specific field from a completed node's output |
$output.{nodeId} | Full output dictionary from a node |
$var.{name} | A workflow variable |
$global.{field} | Execution-wide global data (e.g. trigger payload) |
$context.tenantId | Current tenant ID |
$context.executionId | Current execution ID |