Skip to main content
This example demonstrates building workflows with conditional logic using CrystalFlow’s built-in IfNode and SwitchNode.

Overview

Conditional workflows enable:
  1. Branch execution based on runtime conditions
  2. Skip nodes when conditions aren’t met
  3. Dynamic path selection based on values
  4. 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

  1. BooleanInputNode provides the condition value (true/false)
  2. IfNode receives the condition and optional data
  3. Based on condition:
    • true → Routes to thenOutput
    • false → Routes to elseOutput
  4. 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