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:
Modifier Meaning :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