Skip to main content
This guide covers patterns you’ll use regularly when building decision graphs. Start with the fundamentals (pass-through, $nodes, output organization) before moving to array processing and validation.

Fundamentals

These patterns apply to most decision graphs.

Data flow with passThrough

By default, passThrough is enabled — each node carries forward all previous data plus its own outputs. Disable it when you want to return only the node’s output fields. In the editor, the icon on a node indicates passThrough is enabled. With passThrough (default):
Input: { customer: { tier: "gold" }, order: { total: 150 } }

Decision Table outputs: { discount: 0.15 }

Final output: { customer: { tier: "gold" }, order: { total: 150 }, discount: 0.15 }
Without passThrough:
Input: { customer: { tier: "gold" }, order: { total: 150 } }

Decision Table outputs: { discount: 0.15 }

Final output: { discount: 0.15 }  // Only this node's output
Disable passThrough when:
  • You want to return only the calculated results (common for final nodes)
  • You need to reshape the output structure completely

Self-reference with $

In expression nodes, use $ to reference values calculated earlier in the same node:
subtotal    = sum(map(items, #.price * #.quantity))
tax         = $.subtotal * 0.08
shipping    = $.subtotal > 100 ? 0 : 9.99
total       = $.subtotal + $.tax + $.shipping
Each line can reference values from previous lines using $.fieldName.

Referencing previous nodes with $nodes

Use $nodes to access output from any upstream node in your graph. Each node’s output is available by its name. In expressions:
$nodes.CreditScore.rating        // Output from a node named "CreditScore"
$nodes.IncomeCheck.level         // Output from a node named "IncomeCheck"
$nodes.RiskAssessment.score      // Output from a node named "RiskAssessment"
In function nodes (JavaScript):
export const handler = async (input) => {
  const creditRating = input.$nodes.CreditScore.rating;
  const incomeLevel = input.$nodes.IncomeCheck.level;

  return {
    eligible: creditRating === "good" && incomeLevel === "sufficient"
  };
};
In decision tables: You can reference $nodes in both input conditions and output expressions:
Customer TierCredit RatingDiscount
'gold'$nodes.CreditCheck.rating == 'excellent'0.20
'silver'$nodes.CreditCheck.rating in ['good', 'excellent']0.10
Node names are case-sensitive and must match exactly. If your node is named “Credit Score” with a space, reference it as $nodes["Credit Score"].field.

Organizing output with outputPath

Use outputPath to structure your output into nested objects instead of flat fields. Without outputPath — all outputs merge at root level:
{
  "isEligible": true,
  "code": "eligible",
  "responsibleParty": "seller",
  "refundAmount": 89.99
}
With outputPath — organize related fields into groups:
NodeoutputPathFields
Eligibility checkreturnStatusisEligible, code, message
ResponsibilityresolutionresponsibleParty, refundApproved
Result:
{
  "returnStatus": {
    "isEligible": true,
    "code": "eligible",
    "message": "Return within policy"
  },
  "resolution": {
    "responsibleParty": "seller",
    "refundApproved": true
  }
}
You can also use dot notation in output field definitions like resolution.responsibleParty to achieve the same structure.

Array and collection patterns

These patterns handle lists of items and multiple matching rules.

Processing arrays with loop mode

When your input contains an array of items that each need evaluation, use loop execution mode. The node processes each array element individually and collects the results. Configuration:
PropertyDescription
executionModeSet to loop to iterate over an array
inputFieldPath to the array to process (e.g., testResults, items)
outputPathWhere to store the results array
Loop mode outputs an array at the root level. Without outputPath, you’d get unusable output like [{ flag: "critical" }, { flag: "abnormal" }]. Always specify outputPath to place results in a named field.
Example: Lab results interpreter Input data:
{
  "testResults": [
    { "testType": "glucose", "value": 260 },
    { "testType": "potassium", "value": 3.2 },
    { "testType": "hemoglobin", "value": 10.2 }
  ]
}
Decision table configuration:
  • executionMode: loop
  • inputField: testResults
  • outputPath: testResults
  • passThrough: true
The table evaluates each test result individually:
ValueTest TypeFlagCondition
< 3.5'potassium''critical''Hypokalemia'
> 200'glucose''abnormal''Hyperglycemia'
< 8.5'hemoglobin''abnormal''Anemia'
Output:
{
  "testResults": [
    { "testType": "glucose", "value": 260, "flag": "abnormal", "condition": "Hyperglycemia" },
    { "testType": "potassium", "value": 3.2, "flag": "critical", "condition": "Hypokalemia" },
    { "testType": "hemoglobin", "value": 10.2, "flag": null, "condition": null }
  ]
}

Collecting multiple matches

When multiple rules can apply to a single input, use collect hit policy to return all matching rows as an array.
Hit PolicyBehaviorUse when
firstReturns first matching rowRules are mutually exclusive
collectReturns all matching rows as arrayMultiple rules can apply
Collect mode outputs an array at the root level. Use outputPath to place results in a named field (e.g., discounts.safetyFeatures), otherwise you’ll get [{ percentage: 3 }, { percentage: 2 }] at root.
Example: Safety feature discounts A customer’s vehicle has multiple safety features. Each feature qualifies for a separate discount:
{
  "policy": {
    "safetyFeatures": ["antiTheftSystem", "dashCam", "advancedDriverAssistance"]
  }
}
Decision table with hitPolicy: collect:
Safety FeaturesDiscount %Description
contains($, 'antiTheftSystem')3'Anti-theft discount'
contains($, 'dashCam')2'Dash cam discount'
contains($, 'advancedDriverAssistance')5'ADAS discount'
With outputPath: discounts.safetyFeatures, the output becomes:
{
  "discounts": {
    "safetyFeatures": [
      { "percentage": 3, "description": "Anti-theft discount" },
      { "percentage": 2, "description": "Dash cam discount" },
      { "percentage": 5, "description": "ADAS discount" }
    ]
  }
}
Then sum the discounts in an expression node:
sum(map(discounts.safetyFeatures, #.percentage))  // Returns 10

Workflow patterns

These patterns control the flow of your decision graph.

Conditional branching with switch nodes

Use switch nodes to route data through different paths based on conditions. A switch node has:
  • Conditions: Expressions that determine which path to take
  • Handles: Output connections for each condition
  • Default: Fallback path when no conditions match
Example: Approval workflow
                    ┌─── approved ───→ [Generate Approval]
[Evaluate] → [Switch]
                    └─── rejected ───→ [Generate Rejection]
Switch configuration:
Condition 1: evaluation.isApproved == true  → handle: "approved"
Default:                                     → handle: "rejected"
Each path can have different downstream nodes that produce different outputs. Collect mode for switches: Set hitPolicy: collect on a switch node to execute all matching branches instead of just the first. Results from all branches are merged.

Validation pattern

Validate input early and branch based on validity. Step 1: Validation table — Create a decision table that checks for invalid conditions:
ConditionErrorIsValid
weight <= 0'Weight must be positive'false
weight > 70'Exceeds max weight'false
length > 200'Exceeds max length'false
(empty - catch all)true
Step 2: Branch on validity — Use a switch node to route:
  • Valid requests → continue processing
  • Invalid requests → return error response directly
[Input] → [Validate] → [Switch] ─── valid ───→ [Process] → [Output]
                              └─── invalid ──→ [Output]
This pattern prevents wasted processing on invalid data and provides clear error messages.

Input schema validation

Add a JSON Schema to your input node to validate incoming data structure:
{
  "type": "object",
  "properties": {
    "creditScore": { "type": "number" },
    "annualIncome": { "type": "number" },
    "employmentStatus": { "type": "string" }
  },
  "required": ["creditScore", "annualIncome"]
}
Invalid input is rejected before any processing occurs, with a clear error indicating what’s wrong.

Putting it all together

Real decisions often combine multiple patterns. Here’s a loan approval flow:
[Input with Schema]

[Credit Score Table] ─── passThrough: true ───→ adds creditRating, creditPoints

[Income Table] ─── passThrough: true ───→ adds incomeLevel, incomePoints

[Calculate DTI] ─── passThrough: true ───→ adds dtiRatio

[Rejection Reasons] ─── hitPolicy: collect, outputPath: rejectionReasons

[Switch] ─── len(rejectionReasons) == 0 ───→ [Calculate Interest Rate]
       └─── default ───→ [Return Rejection]
This flow:
  1. Validates input structure via schema
  2. Accumulates scores from multiple evaluation tables
  3. Collects all applicable rejection reasons
  4. Branches based on whether any rejections exist
  5. Returns either approval with rate or rejection with reasons