Conditional Logic

Hyphen supports conditional execution at two levels: gating an entire workflow, and branching individual steps.


Top-Level Conditions

A condition on the workflow definition gates the entire run. If it evaluates to false, the workflow ends immediately with status condition_not_met and no steps execute.

json
{
  "name": "process_high_value",
  "definition": {
    "condition": {
      "greaterThan": ["@input.order_total", 10000]
    },
    "actions": [ ]
  }
}

Use top-level conditions to prevent unnecessary execution — for example, only running a reconciliation workflow when there are actually records to process.


Step-Level Branching: filter + onFalse

Each step can have a filter that determines whether it executes. If the filter is false, the step is skipped — unless an onFalse alternative is defined.

json
{
  "type": "send_notification",
  "filter": {
    "condition": {
      "greaterThan": ["@input.amount", 5000]
    }
  },
  "properties": {
    "channel": "urgent",
    "message": "High-value: ${{input.amount}}"
  },
  "onFalse": {
    "type": "send_notification",
    "properties": {
      "channel": "standard",
      "message": "Processed: ${{input.amount}}"
    }
  }
}
flowchart TD A{"amount > $5,000?"} -->|Yes| B["Send to #urgent"] A -->|No| C["Send to #standard"] B --> D["Next step"] C --> D

The onFalse step has the same structure as any other step — it can have its own type, properties, and even nested filter/onFalse for multi-branch logic.


Comparison Operators

Operator Syntax Description
equal { "equal": [left, right] } Strict equality
notEqual { "notEqual": [left, right] } Not equal
greaterThan { "greaterThan": [left, right] } Greater than (numeric)
lessThan { "lessThan": [left, right] } Less than (numeric)
greaterOrEqual { "greaterOrEqual": [left, right] } Greater or equal
lessOrEqual { "lessOrEqual": [left, right] } Less or equal
hasKey { "hasKey": "path" } Key exists in context
in { "in": [value, array] } Value is in list
matches { "matches": [value, regex] } Regex match

Operands can be literal values or @path references:

json
{ "equal": ["@input.status", "active"] }
{ "greaterThan": ["@input.amount", 1000] }
{ "in": ["@input.country", ["US", "CA", "MX"]] }
{ "matches": ["@input.email", ".*@company\\.com$"] }
{ "hasKey": "@input.metadata.priority" }

The length helper extracts array or string length:

json
{ "greaterThan": [{ "length": "@unmatched_invoices" }, 0] }

Combinators: and, or, not

Combine conditions for complex logic:

and — all conditions must be true:

json
{
  "and": [
    { "greaterThan": ["@input.amount", 1000] },
    { "equal": ["@input.verified", true] }
  ]
}

or — at least one must be true:

json
{
  "or": [
    { "equal": ["@input.customer_type", "premium"] },
    { "greaterThan": ["@input.years_active", 5] }
  ]
}

not — inverts a condition:

json
{
  "not": { "equal": ["@input.status", "cancelled"] }
}

Nested Condition Trees

Combinators nest to any depth. Here's a real-world example that gates an approval step:

Require approval when: amount over $10,000 AND (vendor is new OR country is high-risk) AND the invoice is not already pre-approved.

json
{
  "type": "PbotApproval",
  "filter": {
    "condition": {
      "and": [
        { "greaterThan": ["@input.amount", 10000] },
        {
          "or": [
            { "equal": ["@input.vendor_status", "new"] },
            { "in": ["@input.country", ["RU", "CN", "IR", "KP"]] }
          ]
        },
        {
          "not": { "equal": ["@input.pre_approved", true] }
        }
      ]
    }
  },
  "properties": {
    "comment": "High-value invoice from {{input.vendor_status}} vendor in {{input.country}}",
    "request_payload": {
      "amount": "@input.amount",
      "vendor": "@input.vendor_name",
      "country": "@input.country"
    }
  }
}

Evaluation order. Conditions evaluate depth-first. Inner conditions resolve before outer combinators. All @path references resolve against the current execution context at the time the condition is evaluated.

---

Multi-Branch Pattern

Chain filter/onFalse for multi-way branching:

json
{
  "type": "route_premium",
  "filter": {
    "condition": { "equal": ["@input.tier", "premium"] }
  },
  "properties": { "queue": "premium" },
  "onFalse": {
    "type": "route_standard",
    "filter": {
      "condition": { "equal": ["@input.tier", "standard"] }
    },
    "properties": { "queue": "standard" },
    "onFalse": {
      "type": "route_basic",
      "properties": { "queue": "basic" }
    }
  }
}

This evaluates as: premium → standard → basic (fallback).

→ Next: Scheduling