Skip to content

Subflow Nodes

Subflow nodes invoke another workflow as a subprocess, enabling workflow composition and reuse. A parent workflow can delegate a portion of its logic to a child workflow, passing input and receiving output.

Definition

Subflows are defined using SubflowNodeConfiguration:

dart
final node = LeafWorkflowNode(
  id: 'runValidation',
  type: NodeType.subflow,
  name: 'Run Validation Workflow',
  config: SubflowNodeConfiguration(
    workflowCode: 'VALIDATION',
    executionMode: SubflowExecutionMode.synchronous,
    storeAs: 'validationResult',
  ),
);

Or in JSON:

json
{
  "id": "runValidation",
  "type": "subflow",
  "name": "Run Validation Workflow",
  "config": {
    "schemaType": "config.subflow",
    "workflowCode": "VALIDATION",
    "executionMode": "synchronous",
    "storeAs": "validationResult"
  }
}

Subflow Properties

PropertyTypeDescription
workflowCodeStringCode of the child workflow to invoke (required)
workflowIdString?ID of the child workflow (alternative to workflowCode)
executionModeSubflowExecutionModesynchronous or async
errorStrategyErrorStrategypropagate (fail parent) or route (route to error port)
inputMappingsMap<String, String>Map parent output keys to child input keys (default: {})
outputMappingsMap<String, String>Map child output keys to parent output keys (default: {})
storeAsString?Key for storing child workflow output in parent
timeoutString?Timeout duration string (e.g., "5m", "1h")
completionSignalNameString?Custom signal name for async completion
errorSignalNameString?Custom signal name for async errors
inputSchemaMap<String, dynamic>?Expected input schema for validation
outputSchemaMap<String, dynamic>?Expected output schema for validation

Execution Modes

Synchronous

The parent workflow waits for the child to complete before continuing:

dart
SubflowNodeConfiguration(
  workflowCode: 'VALIDATION',
  executionMode: SubflowExecutionMode.synchronous,
  storeAs: 'validationResult',
)

Async (Fire and Forget)

The parent workflow starts the child and continues immediately without waiting:

dart
SubflowNodeConfiguration(
  workflowCode: 'NOTIFICATION',
  executionMode: SubflowExecutionMode.async,
  storeAs: 'notificationRef',
)

Error Handling

The errorStrategy property controls what happens when the child workflow fails:

Propagate (Default)

Failure in the child workflow fails the parent:

dart
SubflowNodeConfiguration(
  workflowCode: 'PROCESSING',
  executionMode: SubflowExecutionMode.synchronous,
  errorStrategy: ErrorStrategy.propagate,
  storeAs: 'processingResult',
)

Route

Failure routes to the failure outcome port, allowing the parent to handle errors gracefully:

dart
SubflowNodeConfiguration(
  workflowCode: 'PROCESSING',
  executionMode: SubflowExecutionMode.synchronous,
  errorStrategy: ErrorStrategy.route,
  storeAs: 'processingResult',
)

Outcome Ports

Subflow nodes support three outcome ports for routing:

PortIDDescription
SuccesssuccessChild completed successfully
FailurefailureChild failed (only with ErrorStrategy.route)
TimeouttimeoutChild exceeded timeout (only with ErrorStrategy.route and timeout)

Input & Output Mappings

Input Mappings

Map data from the parent's accumulated output to the child's input:

dart
SubflowNodeConfiguration(
  workflowCode: 'INVOICE',
  executionMode: SubflowExecutionMode.synchronous,
  inputMappings: {
    'orderId': 'invoiceOrderId',       // parent's orderId → child's invoiceOrderId
    'customerEmail': 'recipientEmail', // parent's customerEmail → child's recipientEmail
  },
  storeAs: 'invoiceResult',
)

Output Mappings

Map data from the child's output back to the parent:

dart
SubflowNodeConfiguration(
  workflowCode: 'INVOICE',
  executionMode: SubflowExecutionMode.synchronous,
  outputMappings: {
    'invoiceNumber': 'generatedInvoiceNumber',  // child's invoiceNumber → parent's key
    'pdfUrl': 'invoicePdfUrl',
  },
  storeAs: 'invoiceResult',
)

Use Cases

Reusable Validation

Extract common validation logic into a child workflow:

dart
// Parent workflow
final orderNode = LeafWorkflowNode(
  id: 'validateOrder',
  type: NodeType.subflow,
  name: 'Validate Order',
  config: SubflowNodeConfiguration(
    workflowCode: 'ORDER_VALIDATION',
    executionMode: SubflowExecutionMode.synchronous,
    errorStrategy: ErrorStrategy.route,
    inputMappings: {
      'orderId': 'orderId',
      'items': 'orderItems',
    },
    storeAs: 'validationResult',
  ),
);

Async Notification

Fire off a notification workflow without blocking the main process:

dart
final notifyNode = LeafWorkflowNode(
  id: 'sendNotifications',
  type: NodeType.subflow,
  name: 'Send Notifications',
  config: SubflowNodeConfiguration(
    workflowCode: 'NOTIFICATION_BATCH',
    executionMode: SubflowExecutionMode.async,
    inputMappings: {
      'recipientList': 'recipients',
      'templateId': 'notificationTemplate',
    },
  ),
);

Workflow with Timeout

Set a timeout for child workflow execution:

dart
final processingNode = LeafWorkflowNode(
  id: 'processPayment',
  type: NodeType.subflow,
  name: 'Process Payment',
  config: SubflowNodeConfiguration(
    workflowCode: 'PAYMENT',
    executionMode: SubflowExecutionMode.synchronous,
    errorStrategy: ErrorStrategy.route,
    timeout: '5m',
    storeAs: 'paymentResult',
  ),
);

Subflow vs Task

AspectSubflowTask
ScopeEntire workflowSingle operation
CompositionWorkflows within workflowsCode within a node
StateOwn workflow instanceShared parent instance
Error isolationCan propagate or routeDirect failure
ReusabilityAcross multiple parent workflowsVia executor registration
Best forComplex multi-step processesSimple operations

Validation

The executor validates subflow configuration:

  • workflowCode is required; workflowId can optionally override it for exact version targeting
  • The referenced workflow must be registered in the engine
  • inputMappings keys must exist in the parent's accumulated output
  • timeout is only meaningful with synchronous execution mode

Best Practices

  1. Prefer workflowCode over workflowId - Codes are version-independent
  2. Use input/output mappings - Explicit data contracts between parent and child
  3. Set timeouts for synchronous subflows - Prevent indefinite waits
  4. Use ErrorStrategy.route for recoverable failures - Route to alternative paths
  5. Use async mode for fire-and-forget - Notifications, logging, non-critical tasks
  6. Keep subflows focused - Each child workflow should have a single responsibility

Next Steps