Portal Community

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

KeyTypeContents
nodeOutputsDictionary<string, Dictionary<string, object>>Output data keyed by node ID. All completed nodes.
variablesDictionary<string, object>Workflow-scoped variables set by Variable Assignment nodes
globalDataDictionary<string, object>Execution-wide data (trigger payload, initial input)

Expression Syntax Reference

ExpressionResolves 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.tenantIdCurrent tenant ID
$context.executionIdCurrent execution ID
Prefer Expression Resolution Over Direct Memory Access Expression resolution (configured in the Atlas Form) is the preferred pattern. It is declarative, visible in the designer, and survives future engine changes. Direct memory access is appropriate only for dynamic runtime logic that cannot be expressed declaratively.