Skip to main content
GoRules uses a visual decision graph model. Understanding these building blocks helps you work effectively with the platform.

Decision graph

A decision graph is a visual canvas where you connect nodes to model decision logic. Data flows from an input node, through processing nodes, to an output node. Every decision graph has:
  • Input node — Receives the data you want to evaluate
  • Processing nodes — Transform and evaluate the data (decision tables, expressions, functions, switches)
  • Output node — Returns the final result (optional unless you need output validation)
You build graphs by dragging nodes onto the canvas and connecting them. Data flows through the connections, with each node receiving input from the previous node and passing output to the next.

Decision table

A decision table is a spreadsheet-like component for conditional logic. Rows define rules: conditions on the left, outcomes on the right.
Inputs
Outputs
The engine evaluates rows top-to-bottom. By default, it returns the first matching row (first-hit policy). You can also collect all matching rows when needed. When to use: Conditional logic with multiple rules, lookup tables, classification, eligibility checks.

Unary tests vs standard expressions

When an input column has a field name defined, cells use unary test syntax — shorthand expressions evaluated against that field:
OperatorExampleMatches
Comparison> 100, <= 50, != 0Values matching the comparison
Range (inclusive)[1..10]Values from 1 to 10
Range (exclusive)(0..100)Values between 0 and 100
List'US', 'GB', 'CA'Any value in the list
Combined> 5 and < 10Values matching both conditions
Any(empty)Matches any value
When an input column has no field name (empty), cells use standard expressions instead. This lets you write full expressions like customer.age > 18 and customer.country == 'US'.

Hit policies

Hit policies control how the engine handles multiple matching rows:
PolicyBehavior
FirstReturns the first matching row (default)
CollectReturns all matching rows as an array

Expression node

An expression node transforms data using the ZEN expression language. Use it for calculations, mappings, and data manipulation.
Use $ to reference the current expression node’s output. In the example above, $.subtotal refers to the subtotal field calculated earlier in the same node.
The operators and functions below work throughout GoRules — in expression nodes, decision table cells, and switch conditions.

Operators

TypeOperators
Arithmetic+, -, *, /, %, ^ (power)
Comparison==, !=, >, <, >=, <=
Logicaland, or, not
Ternarycondition ? then : else
Null coalescingvalue ?? fallback
Range checkx in [1..10], x not in (0..100)
See Operators for the complete reference.

Built-in functions

CategoryFunctions
Mathabs, floor, ceil, round, min, max, sum, avg, median
Stringlen, upper, lower, trim, contains, startsWith, endsWith, matches, split
Arraymap, filter, some, all, one, none, count, flatMap, keys, values
Dated(), duration
Typestring, number, bool, type, isNumeric
See Built-in functions for the complete reference. When to use: Calculations, data transformation, mapping values, combining fields.

Function node

A function node runs custom JavaScript for complex logic that expressions can’t handle. Use it when you need external API calls, complex algorithms, or operations that require full programming language capabilities.
/** @type {Handler} **/
export const handler = async (input) => {
  const { customerId, orderTotal } = input;

  // Complex loyalty calculation
  const loyaltyPoints = Math.floor(orderTotal * 1.5);

  // Date-based promotions
  const today = dayjs();
  const isWeekend = today.day() === 0 || today.day() === 6;
  const bonusMultiplier = isWeekend ? 2 : 1;

  return {
    loyaltyPoints: loyaltyPoints * bonusMultiplier,
    earnedOn: today.format('YYYY-MM-DD')
  };
};
Function nodes support:
  • ES6+ JavaScript — Modern syntax including async/await
  • Built-in librariesdayjs for dates, big.js for precision math, zod for validation
  • Async operations — Await promises for API calls or async logic
When to use: Complex algorithms, async operations, logic that requires full JavaScript capabilities.

Switch node

A switch node routes data through different paths based on conditions. It evaluates conditions in order and sends data down the first matching branch.
Switch node
Each branch leads to its own chain of nodes, allowing you to build parallel processing paths that merge back into a single output. When to use: Conditional branching, routing logic, different processing paths based on input values.

How data flows

When you evaluate a decision, data flows through the graph:
  1. Input — You provide a JSON object with your data
  2. Processing — Each node receives data, processes it, and passes results forward
  3. Output — The final node returns the decision result
Output node is optional and most often it’s not used. Without one, the engine returns the results from all endpoint nodes combined.

Pass-through behavior

By default, nodes use pass-through mode — they carry forward all incoming data plus their own outputs. This means downstream nodes can access both the original input and any values added by previous nodes.
Input: { customer: { tier: "gold" }, order: { total: 150 } }

Decision Table adds: { discount: 0.15 }

Output: { customer: { tier: "gold" }, order: { total: 150 }, discount: 0.15 }
You can disable pass-through on any node when you want to return only that node’s output fields. The icon on a node indicates pass-through is enabled.
// Input
{
  "customer": { "tier": "gold", "yearsActive": 3 },
  "order": { "subtotal": 150, "items": 5 }
}

// Output (after flowing through the graph)
{
  "discount": 15,
  "freeShipping": true,
  "loyaltyBonus": 225
}
Nodes can access data from:
  • Direct input — Data passed directly into the node
  • Previous nodes — Output from upstream nodes in the graph

Referencing previous nodes

Use $nodes to access the output of any upstream node by its name:
$nodes.DiscountTable.discount      // Output from a node named "DiscountTable"
$nodes.RiskScore.level             // Output from a node named "RiskScore"
$nodes["My Node"].value            // Use brackets for names with spaces
This lets you build multi-stage decisions where later nodes use results from earlier ones. Example: Multi-stage loan decision
[Input] → [Credit Score] → [Income Check] → [Final Decision]
The Credit Score node outputs { rating: "good", score: 720 }. The Income Check node outputs { sufficient: true }. In the Final Decision expression node, you can combine both:
approved = $nodes.CreditScore.rating in ['good', 'excellent'] and $nodes.IncomeCheck.sufficient
In function nodes, access $nodes through the input parameter:
const creditRating = input.$nodes.CreditScore.rating;
const incomeOk = input.$nodes.IncomeCheck.sufficient;

Special symbols

The $ symbol has different meanings depending on context:
SymbolContextMeaning
$Expression nodeReference to current node’s output (e.g., $.subtotal)
$Unary test (decision table)The field value being tested (e.g., len($) > 5)
$nodesAny nodeAccess output from previous nodes
$rootExpression nodeThe entire context object
#Array iterationCurrent element in map, filter, etc.

JDM file format

Decision graphs are stored as JSON Decision Model (JDM) files. This portable format lets you:
  • Version control rules in Git
  • Move rules between environments
  • Share rules across applications
  • Edit rules programmatically
You don’t need to understand the JDM format to use GoRules — the visual editor handles it automatically. But if you want to work with rules programmatically, the format is documented in the JDM Format section.