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:
final node = LeafWorkflowNode(
id: 'runValidation',
type: NodeType.subflow,
name: 'Run Validation Workflow',
config: SubflowNodeConfiguration(
workflowCode: 'VALIDATION',
executionMode: SubflowExecutionMode.synchronous,
storeAs: 'validationResult',
),
);Or in JSON:
{
"id": "runValidation",
"type": "subflow",
"name": "Run Validation Workflow",
"config": {
"schemaType": "config.subflow",
"workflowCode": "VALIDATION",
"executionMode": "synchronous",
"storeAs": "validationResult"
}
}Subflow Properties
| Property | Type | Description |
|---|---|---|
workflowCode | String | Code of the child workflow to invoke (required) |
workflowId | String? | ID of the child workflow (alternative to workflowCode) |
executionMode | SubflowExecutionMode | synchronous or async |
errorStrategy | ErrorStrategy | propagate (fail parent) or route (route to error port) |
inputMappings | Map<String, String> | Map parent output keys to child input keys (default: {}) |
outputMappings | Map<String, String> | Map child output keys to parent output keys (default: {}) |
storeAs | String? | Key for storing child workflow output in parent |
timeout | String? | Timeout duration string (e.g., "5m", "1h") |
completionSignalName | String? | Custom signal name for async completion |
errorSignalName | String? | Custom signal name for async errors |
inputSchema | Map<String, dynamic>? | Expected input schema for validation |
outputSchema | Map<String, dynamic>? | Expected output schema for validation |
Execution Modes
Synchronous
The parent workflow waits for the child to complete before continuing:
SubflowNodeConfiguration(
workflowCode: 'VALIDATION',
executionMode: SubflowExecutionMode.synchronous,
storeAs: 'validationResult',
)Async (Fire and Forget)
The parent workflow starts the child and continues immediately without waiting:
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:
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:
SubflowNodeConfiguration(
workflowCode: 'PROCESSING',
executionMode: SubflowExecutionMode.synchronous,
errorStrategy: ErrorStrategy.route,
storeAs: 'processingResult',
)Outcome Ports
Subflow nodes support three outcome ports for routing:
| Port | ID | Description |
|---|---|---|
| Success | success | Child completed successfully |
| Failure | failure | Child failed (only with ErrorStrategy.route) |
| Timeout | timeout | Child 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:
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:
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:
// 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:
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:
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
| Aspect | Subflow | Task |
|---|---|---|
| Scope | Entire workflow | Single operation |
| Composition | Workflows within workflows | Code within a node |
| State | Own workflow instance | Shared parent instance |
| Error isolation | Can propagate or route | Direct failure |
| Reusability | Across multiple parent workflows | Via executor registration |
| Best for | Complex multi-step processes | Simple operations |
Validation
The executor validates subflow configuration:
workflowCodeis required;workflowIdcan optionally override it for exact version targeting- The referenced workflow must be registered in the engine
inputMappingskeys must exist in the parent's accumulated outputtimeoutis only meaningful withsynchronousexecution mode
Best Practices
- Prefer
workflowCodeoverworkflowId- Codes are version-independent - Use input/output mappings - Explicit data contracts between parent and child
- Set timeouts for synchronous subflows - Prevent indefinite waits
- Use
ErrorStrategy.routefor recoverable failures - Route to alternative paths - Use async mode for fire-and-forget - Notifications, logging, non-critical tasks
- Keep subflows focused - Each child workflow should have a single responsibility
Next Steps
- Timer Event - Time-based waits
- Loop Nodes - Iteration patterns
- Error Handling - Error strategies
- Control Flow - Workflow patterns