Flow Studio
Returning Results
NodeExecutionResult is what your executor returns from ExecuteInternalAsync. It tells the engine which output port to follow next, what data to store in ExecutionMemory, and whether the node succeeded or failed.
NodeExecutionResult Shape
public class NodeExecutionResult
{
// Output data — stored as $output.{nodeId} in ExecutionMemory
public Dictionary<string, object> OutputData { get; set; } = new();
// Which output port the engine should follow next
// Typical values: "main", "error", "true", "false", or a portKey
public string OutputPortKey { get; set; } = "main";
// Whether the node succeeded
public bool IsSuccess { get; set; } = true;
// Whether this node was skipped (branch not taken)
public bool IsSkipped { get; set; } = false;
// Error message (set when IsSuccess = false)
public string? ErrorMessage { get; set; }
// Exception object (captured, not rethrown)
public Exception? Exception { get; set; }
// Binary output (e.g. file bytes for download/pass-through)
public byte[]? OutputBinary { get; set; }
}
Success Result
// Minimal success — engine follows the "main" output port
return new NodeExecutionResult
{
IsSuccess = true,
OutputPortKey = "main",
OutputData = new Dictionary<string, object>
{
{ "statusCode", 200 },
{ "body", responseBody },
{ "headers", responseHeaders }
}
};
Error Result (Error Port)
// Error — engine follows the "error" output port if one is connected
return new NodeExecutionResult
{
IsSuccess = false,
OutputPortKey = ExecutionConstants.OutputPorts.Error, // "error"
ErrorMessage = "External API returned 500: Internal Server Error",
OutputData = new Dictionary<string, object>
{
{ "status", "error" },
{ "errorCode", "API_500" },
{ "message", errorText }
}
};
Conditional Branch Result
Decision nodes (if-condition, switch) return results where OutputPortKey indicates which branch was taken:
// If-condition node — true branch
return new NodeExecutionResult
{
IsSuccess = true,
OutputPortKey = "true", // follows the "true" output handle
OutputData = new Dictionary<string, object> { { "matched", true } }
};
// Switch node — specific case
return new NodeExecutionResult
{
IsSuccess = true,
OutputPortKey = "case-premium", // matches a portKey on the switch node
OutputData = new Dictionary<string, object> { { "tier", "premium" } }
};
Skip Result
A skipped result means the node ran but produced no meaningful output. The engine counts it as completed and continues without routing:
// Skip — node execution was a no-op (e.g. loop has no items)
return new NodeExecutionResult
{
IsSuccess = true,
IsSkipped = true,
OutputPortKey = "main",
OutputData = new Dictionary<string, object>()
};
OutputPortKey Convention
| OutputPortKey | When to Use |
|---|---|
"main" | Success path — most nodes (default) |
"error" | Error path — only if node has an error output port configured |
"true" / "false" | If-condition branches |
| Custom portKey | Switch branches, multi-output nodes — must match ProcessElementPort.portKey |
OutputData Becomes $output.{nodeId}
Everything in
OutputData is accessible downstream via $output.{nodeId}.{field} expressions. Keep keys descriptive and flat where possible. Nested objects are supported but make expressions more verbose.