> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gorules.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Building decision tables

> Create spreadsheet-style business rules with conditions and outcomes.

export const DecisionTableViz = ({inputs = [], outputs = [], rows = []}) => {
  const colCount = inputs.length + outputs.length;
  const getFieldDisplay = field => !field || field === '-' ? '-' : field;
  const allCells = rows.flatMap((row, ri) => [...inputs.map((input, i) => <div key={`c-${ri}-in-${i}`} style={{
    backgroundColor: '#ffffff',
    padding: '12px 16px',
    borderRadius: '8px',
    fontFamily: 'ui-monospace, monospace',
    fontSize: '14px',
    border: '1px solid #e2e8f0'
  }}>
        {row[input.field] ?? ''}
      </div>), ...outputs.map((output, i) => <div key={`c-${ri}-out-${i}`} style={{
    backgroundColor: '#ffffff',
    padding: '12px 16px',
    borderRadius: '8px',
    fontFamily: 'ui-monospace, monospace',
    fontSize: '14px',
    border: '1px solid #e2e8f0'
  }}>
        {row[output.field] ?? ''}
      </div>)]);
  return <div style={{
    backgroundColor: '#f1f5f9',
    borderRadius: '12px',
    padding: '20px',
    overflowX: 'auto',
    margin: '16px 0'
  }}>
      <div style={{
    display: 'grid',
    gridTemplateColumns: `repeat(${colCount}, 1fr)`,
    gap: '8px'
  }}>
        {}
        <div key="group-inputs" style={{
    gridColumn: `span ${inputs.length}`,
    padding: '8px 16px',
    fontWeight: '600',
    fontSize: '13px',
    color: '#374151',
    borderBottom: '2px solid #e2e8f0'
  }}>
          Inputs
        </div>
        <div key="group-outputs" style={{
    gridColumn: `span ${outputs.length}`,
    padding: '8px 16px',
    fontWeight: '600',
    fontSize: '13px',
    color: '#374151',
    borderBottom: '2px solid #e2e8f0'
  }}>
          Outputs
        </div>

        {}
        {inputs.map((input, i) => <div key={`h-in-${i}`} style={{
    backgroundColor: '#f0abfc',
    color: '#581c87',
    padding: '12px 16px',
    borderRadius: '8px',
    fontWeight: '500'
  }}>
            <div>{input.name}</div>
            <div style={{
    display: 'inline-block',
    marginTop: '6px',
    padding: '2px 8px',
    fontSize: '12px',
    fontWeight: '400',
    backgroundColor: 'rgba(88, 28, 135, 0.15)',
    borderRadius: '4px',
    fontFamily: 'ui-monospace, monospace'
  }}>
              {getFieldDisplay(input.field)}
            </div>
          </div>)}
        {outputs.map((output, i) => <div key={`h-out-${i}`} style={{
    backgroundColor: '#a5b4fc',
    color: '#312e81',
    padding: '12px 16px',
    borderRadius: '8px',
    fontWeight: '500'
  }}>
            <div>{output.name}</div>
            <div style={{
    display: 'inline-block',
    marginTop: '6px',
    padding: '2px 8px',
    fontSize: '12px',
    fontWeight: '400',
    backgroundColor: 'rgba(49, 46, 129, 0.15)',
    borderRadius: '4px',
    fontFamily: 'ui-monospace, monospace'
  }}>
              {getFieldDisplay(output.field)}
            </div>
          </div>)}

        {}
        {allCells}
      </div>
    </div>;
};

Decision tables let you define business rules in a familiar spreadsheet format. Each row is a rule: conditions on the left, outcomes on the right.

## Creating a decision table

1. Open your decision graph
2. Drag a **Decision Table** node onto the canvas
3. Connect it to your Input node (or other upstream nodes)
4. Click **Edit Table** to open the editor

## Hit policies

Hit policies control what happens when multiple rows match.

| Policy      | Behavior                                 |
| ----------- | ---------------------------------------- |
| **First**   | Returns the first matching row (default) |
| **Collect** | Returns all matching rows as an array    |

To change the hit policy, click on the **Settings** on the decision graph node.

### First hit (default)

<video autoPlay loop muted playsInline style={{ width: '100%', borderRadius: '8px' }}>
  <source src="https://mintcdn.com/gorules/_eSfZVSvlKqfwnSP/videos/DecisionTable.mp4?fit=max&auto=format&n=_eSfZVSvlKqfwnSP&q=85&s=37fc9f4f6ffed8f16ad0d301e6c4d342" type="video/mp4" data-path="videos/DecisionTable.mp4" />
</video>

The engine evaluates rows top to bottom and stops at the first match. Order your rows from most specific to most general:

### Collect

<video autoPlay loop muted playsInline style={{ width: '100%', borderRadius: '8px' }}>
  <source src="https://mintcdn.com/gorules/_eSfZVSvlKqfwnSP/videos/DecisionTableCollect.mp4?fit=max&auto=format&n=_eSfZVSvlKqfwnSP&q=85&s=552a62c4ee9776ea484d06e240e37b11" type="video/mp4" data-path="videos/DecisionTableCollect.mp4" />
</video>

Returns all matching rows. Useful when you need to apply multiple rules:

* Calculate all applicable fees
* Find all matching promotions
* Aggregate scores from multiple criteria

### When no rows match

If no rows match the input:

* **First hit policy** — Returns an empty object `{}`
* **Collect policy** — Returns an empty array `[]`

To handle this, add a **catch-all row** at the bottom with empty conditions that matches any input:

<DecisionTableViz
  inputs={[
{ name: "Status", field: "status" }
]}
  outputs={[
{ name: "Action", field: "action" }
]}
  rows={[
{ status: '"active"', action: '"process"' },
{ status: '"pending"', action: '"queue"' },
{ status: "", action: '"unknown"' }
]}
/>

The last row with empty conditions acts as a default, ensuring you always get a meaningful result.

## Adding columns

Decision tables have two column types:

**Input columns** — Define conditions to match against incoming data
**Output columns** — Define values to return when conditions match

To add a column:

1. Click **+** in the header row
2. Select **Input** or **Output**
3. Enter the field path (e.g., `customer.tier` or `order.total`)
4. Give it a readable label

## Input column types

Input columns can be configured in two modes:

### Targeted field (unary)

The default mode. Configure a field path (like `customer.revenue`) in the column settings, then write simple conditions in each cell:

<DecisionTableViz
  inputs={[
{ name: "Customer Revenue", field: "customer.revenue" }
]}
  outputs={[
{ name: "Tier", field: "tier" }
]}
  rows={[
{ "customer.revenue": "> 6000", tier: "'gold'" },
{ "customer.revenue": "< 5000", tier: "'bronze'" },
{ "customer.revenue": "", tier: "'silver'" }
]}
/>

The field path is evaluated automatically — you only write the comparison operator and value.

### Generic field (standard)

Set the field to `-` (empty) to write full expressions in each cell:

<DecisionTableViz
  inputs={[
{ name: "Condition", field: "-" }
]}
  outputs={[
{ name: "Tier", field: "tier" }
]}
  rows={[
{ "-": "customer.revenue > 6000 and customer.status == 'active'", tier: "'gold'" },
{ "-": "customer.type == 'enterprise'", tier: "'gold'" },
{ "-": "", tier: "'standard'" }
]}
/>

Use generic columns when you need to:

* Compare multiple fields in one condition
* Write complex expressions that don't fit the unary pattern
* Reference previous nodes with `$nodes`

## Writing conditions

Input columns use **unary test syntax** — shorthand expressions evaluated against each cell's value.

<DecisionTableViz
  inputs={[
{ name: "Credit Score", field: "applicant.creditScore" },
{ name: "Debt Ratio", field: "applicant.dti" }
]}
  outputs={[
{ name: "Decision", field: "decision" },
{ name: "Rate", field: "rate" }
]}
  rows={[
{ "applicant.creditScore": ">= 700", "applicant.dti": "< 0.4", decision: '"approve"', rate: "4.5" },
{ "applicant.creditScore": "[650..699]", "applicant.dti": "< 0.4", decision: '"approve"', rate: "6.2" },
{ "applicant.creditScore": "[600..649]", "applicant.dti": "< 0.35", decision: '"review"', rate: "7.8" },
{ "applicant.creditScore": "< 600", "applicant.dti": "", decision: '"deny"', rate: "" }
]}
/>

### Condition syntax

| Type              | Syntax                           | Example            | Matches                              |
| ----------------- | -------------------------------- | ------------------ | ------------------------------------ |
| Comparison        | `>`, `<`, `>=`, `<=`, `==`, `!=` | `>= 100`           | Values 100 or greater                |
| Range (inclusive) | `[min..max]`                     | `[18..65]`         | Values from 18 to 65                 |
| Range (exclusive) | `(min..max)`                     | `(0..100)`         | Values between 0 and 100             |
| List              | `'a', 'b', 'c'`                  | `'US', 'CA', 'GB'` | Any listed value                     |
| Combined          | `and`, `or`                      | `> 10 and < 50`    | Values matching both                 |
| With functions    | `$`                              | `len($) > 5`       | Use `$` to reference the field value |
| Any value         | *(empty)*                        |                    | Matches everything                   |

<Note>
  When you use `$` in a targeted field column, the expression is treated as a standard expression. This lets you use functions like `len($)`, `contains($, 'text')`, or `upper($) == 'VALUE'`.
</Note>

## Writing outputs

Output columns contain the values returned when a row matches. You can use:

* **Literal values** — `100`, `"approved"`, `true`
* **Expressions** — `input.amount * 0.1`
* **References** — `customer.defaultRate`

## Referencing previous nodes

Use `$nodes` to access output from upstream nodes in your graph.

### In conditions (generic columns)

<DecisionTableViz
  inputs={[
{ name: "Approval Check", field: "-" }
]}
  outputs={[
{ name: "Approved", field: "approved" }
]}
  rows={[
{ "-": "$nodes.CreditCheck.score > 700", approved: "true" },
{ "-": "$nodes.RiskAssessment.level == 'low'", approved: "true" },
{ "-": "", approved: "false" }
]}
/>

### In outputs

<DecisionTableViz
  inputs={[
{ name: "Annual Income", field: "applicant.income" }
]}
  outputs={[
{ name: "Interest Rate", field: "rate" }
]}
  rows={[
{ "applicant.income": "> 100000", rate: "$nodes.BaseRates.premium" },
{ "applicant.income": "> 50000", rate: "$nodes.BaseRates.standard" },
{ "applicant.income": "", rate: "$nodes.BaseRates.basic" }
]}
/>

This lets you build multi-stage decisions where each table can use results from earlier nodes.

## Testing your table

1. Click **Open Simulator** in the toolbar
2. Enter test input as JSON
3. Click **Run**
4. View the matched row and output

The simulator highlights which row matched and shows the trace through your decision.

## Best practices

**Order rows by specificity** — Put specific conditions before general ones when using first-hit policy.

**Use meaningful labels** — Column labels appear in the UI and help others understand your rules.

**Add a catch-all row** — End with a row using empty cells in all inputs to handle unexpected cases.

**Keep tables focused** — If a table grows beyond 20-30 rows, consider splitting it into multiple tables or using a switch node.
