Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.penbox.io/llms.txt

Use this file to discover all available pages before exploring further.

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

Variables & scope

Variable references and scope

Numbers & calculations

Arithmetic and formatting

Loops & arrays

Working with collections

How penscript works

Expression modes and composition