Skip to main content
Conditional logic is the foundation of dynamic behavior in penscript. These operators control which content appears, which rules apply, and which branches a workflow follows.

Conditional: :if

Evaluates a condition and returns one of two branches.
{
  ":if": "{data.is_active}",
  ":then": "Active",
  ":else": "Inactive"
}

// Scope: { "data": { "is_active": true } }
// Result: "Active"
The condition can be a variable, an inline expression, or another operator:
{
  ":if": { ":cmp": "{data.count}", ":gt": 0 },
  ":then": "There are {data.count} items",
  ":else": "No items"
}

// Scope: { "data": { "count": 3 } }
// Result: "There are 3 items"
Multiple conditions can be passed as an array — all must be true for the :then branch:
{
  ":if": [
    { ":cmp": "{data.age}", ":gte": 18 },
    { ":in": ["{data.country}", ["BE", "FR", "LU"]] }
  ],
  ":then": "Eligible",
  ":else": "Not eligible"
}
The return values can be any type — strings, numbers, objects, arrays, or nested operators:
{
  ":if": "{data.premium_member}",
  ":then": ["feature_a", "feature_b", "feature_c"],
  ":else": "{data.default_features}"
}

// Scope: { "data": { "premium_member": true } }
// Result: ["feature_a", "feature_b", "feature_c"]

Switch: :case

Matches a value against a list of options. Returns the result for the first match, or the :else fallback if nothing matches.
{
  ":case": "{data.status}",
  ":when": [
    ["pending", "Status is pending"],
    ["done", "Status is done"],
    ["error", "Status is error"]
  ],
  ":else": "Status is unknown"
}

// Scope: { "data": { "status": "done" } }
// Result: "Status is done"
Cleaner than nested :if chains when you have multiple known values to match against.

Comparison: :cmp

Compares a value against one or more thresholds. Returns true or false. Supported modifiers:
ModifierMeaning
:eqEqual to
:neqNot equal to
:gtGreater than
:gte / :geGreater than or equal to
:ltLess than
:lte / :leLess than or equal to
Single threshold:
{ ":cmp": "{data.count}", ":gt": 0 }
// Scope: { "data": { "count": 5 } }
// Result: true

{ ":cmp": "{data.count}", ":eq": 3 }
// Scope: { "data": { "count": 5 } }
// Result: false
Multiple thresholds — all must be satisfied:
{ ":cmp": "{data.count}", ":gt": 0, ":lt": 10 }
// Scope: { "data": { "count": 5 } }
// Result: true   (5 > 0 AND 5 < 10)

{ ":cmp": "{data.age}", ":gte": 18, ":lte": 65 }
// Range check: is the value between 18 and 65 inclusive?
Most commonly used as the condition inside :if:
{
  ":if": { ":cmp": "{data.score}", ":gte": 50 },
  ":then": "Passed",
  ":else": "Failed"
}
Works with numbers, dates, and values produced by pipes like | age.

Equality: :eq

Tests whether all values in a list are equal to each other.
{ ":eq": ["{data.a}", "{data.b}", 42] }

// Scope: { "data": { "a": 42, "b": 42 } }
// Result: true

{ ":eq": [1, 2] }
// Result: false
Useful for validating that multiple fields contain the same value (e.g., password confirmation, duplicate detection).

Uniqueness: :distinct

Returns true if all values in the list are different from each other.
{ ":distinct": [1, 2, 3] }
// Result: true

{ ":distinct": [1, 1, 2] }
// Result: false
Useful for dynamic validation — ensuring all generated fields have unique values:
{
  "required": {
    ":distinct": ["{@index}", { ":sum": ["{@array.length}", -1] }]
  }
}

Membership: :in / :nin

Tests whether a value exists (or doesn’t exist) in a given list. :in — value is in the list:
{ ":in": ["{data.role}", ["admin", "manager"]] }

// Scope: { "data": { "role": "admin" } }
// Result: true
:nin — value is NOT in the list:
{ ":nin": ["{data.role}", ["banned", "suspended"]] }

// Scope: { "data": { "role": "user" } }
// Result: true
Works naturally with :if for access control and routing:
{
  ":if": { ":in": ["{data.country}", ["FR", "BE", "CH"]] },
  ":then": "French-speaking region",
  ":else": "Other region"
}

// Scope: { "data": { "country": "BE" } }
// Result: "French-speaking region"

Boolean Helpers

:not

Negates a boolean value.
{ ":not": "{data.is_blocked}" }

// Scope: { "data": { "is_blocked": true } }
// Result: false

:every

Returns true if every item in an array satisfies a condition. The condition is evaluated for each item with @item as the current element.
{
  ":every": [
    "{data.scores}",
    { ":cmp": "@item", ":ge": 10 }
  ]
}

// Scope: { "data": { "scores": [10, 12, 15] } }
// Result: true   (all scores ≥ 10)

:some

Returns true if at least one item satisfies a condition.
{
  ":some": [
    "{data.users}",
    { ":eq": ["@item.role", "admin"] }
  ]
}

// Scope: {
//   "data": {
//     "users": [
//       { "id": 1, "role": "user" },
//       { "id": 2, "role": "admin" }
//     ]
//   }
// }
// Result: true   (at least one admin exists)

Logical Constants

Explicit boolean and null values for use in expressions where you need to return a fixed value.
{ ":true": null }       // → true
{ ":false": null }      // → false
{ ":null": null }       // → null
{ ":undefined": null }  // → undefined
These are typically used inside :if branches or :map transformations when you need to produce a specific constant value programmatically.

Next Steps