This example demonstrates building workflows with conditional logic using CrystalFlow’s built-in IfNode and SwitchNode.
Overview
Conditional workflows enable:
- Branch execution based on runtime conditions
- Skip nodes when conditions aren’t met
- Dynamic path selection based on values
- Multi-way routing with switch/case logic
Basic If/Else Flow
The IfNode provides classic if/else conditional branching:
import { Workflow, IfNode, BooleanInputNode, DisplayNode } from '@crystalflow/core';
const workflow = new Workflow('if-else-example');
// Input: true/false
const boolInput = workflow.addNode(BooleanInputNode, {
position: { x: 100, y: 200 }
});
// If node: routes based on condition
const ifNode = workflow.addNode(IfNode, {
position: { x: 300, y: 200 }
});
// Then branch
const thenDisplay = workflow.addNode(DisplayNode, {
position: { x: 500, y: 100 }
});
// Else branch
const elseDisplay = workflow.addNode(DisplayNode, {
position: { x: 500, y: 300 }
});
// Connect
workflow.connect(boolInput.id, 'value', ifNode.id, 'condition');
workflow.connect(boolInput.id, 'value', ifNode.id, 'value'); // Pass data through
workflow.connect(ifNode.id, 'thenOutput', thenDisplay.id, 'message');
workflow.connect(ifNode.id, 'elseOutput', elseDisplay.id, 'message');
// Execute
const result = await workflow.execute();
// If condition = true: thenDisplay shows "true"
// If condition = false: elseDisplay shows "false"
How It Works
BooleanInputNode provides the condition value (true/false)
IfNode receives the condition and optional data
- Based on condition:
true → Routes to thenOutput
false → Routes to elseOutput
- Only the active branch executes - the other is skipped
Only nodes connected to the active output execute. If condition is true,
nodes on the else branch never run!
Numeric Comparison
Use comparison nodes to generate boolean conditions:
import { NumberInputNode, CompareNode, IfNode, DisplayNode } from '@crystalflow/core';
const workflow = new Workflow('comparison-example');
// Input number
const numberInput = workflow.addNode(NumberInputNode);
// Compare: number > 10?
const compareNode = workflow.addNode(CompareNode);
compareNode.setProperty('operator', '>');
compareNode.setProperty('b', 10);
// If node
const ifNode = workflow.addNode(IfNode);
// Results
const highDisplay = workflow.addNode(DisplayNode, {
data: { prefix: 'High: ' }
});
const lowDisplay = workflow.addNode(DisplayNode, {
data: { prefix: 'Low: ' }
});
// Connect
workflow.connect(numberInput.id, 'value', compareNode.id, 'a');
workflow.connect(compareNode.id, 'result', ifNode.id, 'condition');
workflow.connect(numberInput.id, 'value', ifNode.id, 'value');
workflow.connect(ifNode.id, 'thenOutput', highDisplay.id, 'message');
workflow.connect(ifNode.id, 'elseOutput', lowDisplay.id, 'message');
// Execute with value = 15
// Result: "High: 15" (because 15 > 10)
Available Comparison Operators
> - Greater than
>= - Greater than or equal
< - Less than
<= - Less than or equal
== - Equal
!= - Not equal
Switch/Case Routing
The SwitchNode provides multi-way branching:
import { Workflow, SwitchNode, StringInputNode, DisplayNode } from '@crystalflow/core';
const workflow = new Workflow('switch-example');
// Input: status value
const statusInput = workflow.addNode(StringInputNode);
// Switch: route by status
const switchNode = workflow.addNode(SwitchNode);
switchNode.setProperty('cases', ['pending', 'approved', 'rejected']);
// Handlers for each case
const pendingHandler = workflow.addNode(DisplayNode, {
data: { prefix: 'Pending: ' }
});
const approvedHandler = workflow.addNode(DisplayNode, {
data: { prefix: 'Approved: ' }
});
const rejectedHandler = workflow.addNode(DisplayNode, {
data: { prefix: 'Rejected: ' }
});
const defaultHandler = workflow.addNode(DisplayNode, {
data: { prefix: 'Unknown: ' }
});
// Connect
workflow.connect(statusInput.id, 'value', switchNode.id, 'value');
workflow.connect(statusInput.id, 'value', switchNode.id, 'data');
workflow.connect(switchNode.id, 'case_0', pendingHandler.id, 'message'); // 'pending'
workflow.connect(switchNode.id, 'case_1', approvedHandler.id, 'message'); // 'approved'
workflow.connect(switchNode.id, 'case_2', rejectedHandler.id, 'message'); // 'rejected'
workflow.connect(switchNode.id, 'default', defaultHandler.id, 'message');
// Execute with value = 'approved'
// Result: "Approved: approved" (only approvedHandler runs)
Dynamic Outputs
SwitchNode generates outputs dynamically based on the cases property:
- 3 cases → 4 outputs:
case_0, case_1, case_2, default
- Add case → new output appears automatically
- Remove case → output disappears
// Configure cases in property panel
switchNode.setProperty('cases', ['apple', 'banana', 'orange', 'grape']);
// Generates outputs:
// - case_0 (apple)
// - case_1 (banana)
// - case_2 (orange)
// - case_3 (grape)
// - default
Nested Conditionals
Conditional nodes can be nested for complex decision trees:
If Inside If
const workflow = new Workflow('nested-if-example');
// Check if positive
const outerIf = workflow.addNode(IfNode);
outerIf.name = 'CheckPositive';
// Check if even (inside then branch)
const innerIf = workflow.addNode(IfNode);
innerIf.name = 'CheckEven';
// Handlers
const positiveEvenHandler = workflow.addNode(DisplayNode);
const positiveOddHandler = workflow.addNode(DisplayNode);
const negativeHandler = workflow.addNode(DisplayNode);
// Connect outer if
workflow.connect(numberInput.id, 'value', outerIf.id, 'condition'); // isPositive?
workflow.connect(outerIf.id, 'thenOutput', innerIf.id, 'condition'); // isEven?
workflow.connect(outerIf.id, 'elseOutput', negativeHandler.id, 'message');
// Connect inner if
workflow.connect(innerIf.id, 'thenOutput', positiveEvenHandler.id, 'message');
workflow.connect(innerIf.id, 'elseOutput', positiveOddHandler.id, 'message');
// Execution paths:
// number > 0 AND even → positiveEvenHandler
// number > 0 AND odd → positiveOddHandler
// number <= 0 → negativeHandler
Switch Inside If
const workflow = new Workflow('switch-in-if-example');
// Authentication check
const authCheck = workflow.addNode(IfNode);
authCheck.name = 'CheckAuthenticated';
// Role routing (inside then branch)
const roleSwitch = workflow.addNode(SwitchNode);
roleSwitch.setProperty('cases', ['admin', 'user', 'guest']);
// Handlers
const adminHandler = workflow.addNode(DisplayNode);
const userHandler = workflow.addNode(DisplayNode);
const guestHandler = workflow.addNode(DisplayNode);
const loginHandler = workflow.addNode(DisplayNode);
// Connect
workflow.connect(authCheck.id, 'thenOutput', roleSwitch.id, 'value');
workflow.connect(authCheck.id, 'elseOutput', loginHandler.id, 'message');
workflow.connect(roleSwitch.id, 'case_0', adminHandler.id, 'message');
workflow.connect(roleSwitch.id, 'case_1', userHandler.id, 'message');
workflow.connect(roleSwitch.id, 'case_2', guestHandler.id, 'message');
// Execution:
// authenticated + role='admin' → adminHandler
// authenticated + role='user' → userHandler
// not authenticated → loginHandler
Advanced Patterns
Multi-Stage Validation
Validate data through multiple conditional checks:
const workflow = new Workflow('validation-workflow');
// Stage 1: Check if value exists
const checkExists = workflow.addNode(IfNode);
// Stage 2: Check if value is valid type
const checkType = workflow.addNode(IfNode);
// Stage 3: Check if value in range
const checkRange = workflow.addNode(IfNode);
// Handlers
const successHandler = workflow.addNode(DisplayNode);
const missingHandler = workflow.addNode(DisplayNode);
const invalidTypeHandler = workflow.addNode(DisplayNode);
const outOfRangeHandler = workflow.addNode(DisplayNode);
// Connect: exists? → validType? → inRange? → success
workflow.connect(checkExists.id, 'thenOutput', checkType.id, 'condition');
workflow.connect(checkExists.id, 'elseOutput', missingHandler.id, 'message');
workflow.connect(checkType.id, 'thenOutput', checkRange.id, 'condition');
workflow.connect(checkType.id, 'elseOutput', invalidTypeHandler.id, 'message');
workflow.connect(checkRange.id, 'thenOutput', successHandler.id, 'message');
workflow.connect(checkRange.id, 'elseOutput', outOfRangeHandler.id, 'message');
State Machine
Implement state transitions with switch:
const workflow = new Workflow('state-machine');
const currentState = workflow.addNode(StringInputNode);
const eventInput = workflow.addNode(StringInputNode);
const stateSwitch = workflow.addNode(SwitchNode);
stateSwitch.setProperty('cases', ['idle', 'loading', 'success', 'error']);
// State handlers
const idleHandler = workflow.addNode(ProcessIdleNode);
const loadingHandler = workflow.addNode(ProcessLoadingNode);
const successHandler = workflow.addNode(ProcessSuccessNode);
const errorHandler = workflow.addNode(ProcessErrorNode);
workflow.connect(currentState.id, 'value', stateSwitch.id, 'value');
workflow.connect(eventInput.id, 'value', stateSwitch.id, 'data');
workflow.connect(stateSwitch.id, 'case_0', idleHandler.id, 'event');
workflow.connect(stateSwitch.id, 'case_1', loadingHandler.id, 'event');
workflow.connect(stateSwitch.id, 'case_2', successHandler.id, 'event');
workflow.connect(stateSwitch.id, 'case_3', errorHandler.id, 'event');
// Each handler can transition to new state and emit events
React Component Example
import React from 'react';
import { WorkflowBuilder } from '@crystalflow/react';
import {
IfNode,
SwitchNode,
BooleanInputNode,
NumberInputNode,
StringInputNode,
CompareNode,
DisplayNode
} from '@crystalflow/core';
function ConditionalWorkflowExample() {
const nodes = [
// Flow Control
IfNode,
SwitchNode,
// Input nodes
BooleanInputNode,
NumberInputNode,
StringInputNode,
// Logic nodes
CompareNode,
// Output
DisplayNode
];
const handleExecute = (result) => {
console.log('Workflow executed:', result);
};
return (
<div className="conditional-workflow-example">
<h1>Conditional Logic Workflow</h1>
<p>
Drag IfNode and SwitchNode from the Flow Control category to create
branching workflows.
</p>
<WorkflowBuilder
nodes={nodes}
onExecute={handleExecute}
showNodePalette={true}
showPropertyPanel={true}
style={{ height: '600px' }}
/>
<div className="instructions">
<h3>Try These Workflows:</h3>
<ol>
<li><strong>Simple If/Else:</strong> BooleanInput → If → Display (then/else)</li>
<li><strong>Comparison:</strong> NumberInput → Compare → If → Display</li>
<li><strong>Switch Routing:</strong> StringInput → Switch (3 cases) → Multiple Displays</li>
<li><strong>Nested:</strong> If → If (nested in then branch)</li>
</ol>
</div>
</div>
);
}
export default ConditionalWorkflowExample;
Try It Yourself
The CrystalFlow examples package includes a complete ConditionalNodesDemo.tsx with:
- All logic test nodes (CompareNode, IsPositiveNode, IsEvenNode, etc.)
- IfNode and SwitchNode from @crystalflow/core
- 7 documented workflow scenarios
- Full interactive UI with property panel for configuring cases
Run the examples:
cd packages/examples
npm run dev
# Navigate to the Conditional Flow demo
Key Concepts
Branch Evaluation
evaluateCondition() determines which branch executes at runtime
Single Branch Execution
Only the active branch runs - others are completely skipped
Dynamic Outputs
SwitchNode outputs update automatically when cases change
Nested Conditionals
Conditionals can be nested arbitrarily deep for complex logic
Next Steps