Skip to content

Boolean Expressions Guide

Boolean expressions allow you to evaluate complex logical conditions directly within placeholders. Combine variables with operators to create dynamic, conditional content without using separate {{#if}} blocks.

Table of Contents

Quick Start

Wrap any boolean expression in parentheses within a placeholder:

Template:

Eligible: {{(Age >= 18)}}

C# Data:

var data = new Dictionary<string, object>
{
    ["Age"] = 25
};

JSON Data:

{
  "Age": 25
}

Output:

Eligible: True

Expression Syntax

Basic Rules

  1. Expressions must start with ( - This distinguishes them from simple variables
  2. Expressions must end with ) - Properly close the parentheses
  3. Operators are case-insensitive - and, AND, And all work
  4. Word operators need spaces - (var1 and var2) works, (var1and var2) does not (var1and is read as one name). Symbol operators do not need spaces: (Count>0) works
  5. No } inside the expression - the placeholder ends at the first }
  6. Result is a boolean - rendered as True/False, or through a boolean format specifier such as :yesno

If an expression cannot be evaluated (for example {{(Count >)}} or {{(A && B)}}), it is handled like a missing variable: by default the placeholder stays in the document, and processing reports an ExpressionFailed and a MissingVariable warning.

Expression vs. Simple Variable

Simple Variable (no evaluation):

Status: {{IsActive}}

Expression (evaluated):

Status: {{(IsActive)}}

Complex Expression:

Status: {{(IsActive and IsVerified)}}

Logical Operators

AND Operator

Returns true only if both operands are true.

Syntax: and

Truth Table:

Left Right Result
true true true
true false false
false true false
false false false

Template:

Access granted: {{(IsActive and IsVerified)}}

C# Data:

var data = new Dictionary<string, object>
{
    ["IsActive"] = true,
    ["IsVerified"] = true
};

JSON Data:

{
  "IsActive": true,
  "IsVerified": true
}

Output:

Access granted: True

OR Operator

Returns true if at least one operand is true.

Syntax: or

Truth Table:

Left Right Result
true true true
true false true
false true true
false false false

Template:

Can proceed: {{(HasPermissionA or HasPermissionB)}}

C# Data:

var data = new Dictionary<string, object>
{
    ["HasPermissionA"] = false,
    ["HasPermissionB"] = true
};

JSON Data:

{
  "HasPermissionA": false,
  "HasPermissionB": true
}

Output:

Can proceed: True

NOT Operator

Returns the opposite of the operand.

Syntax: not

Truth Table:

Value Result
true false
false true

Template:

Account locked: {{(not IsActive)}}

C# Data:

var data = new Dictionary<string, object>
{
    ["IsActive"] = false
};

JSON Data:

{
  "IsActive": false
}

Output:

Account locked: True

Comparison Operators

Equality (== or =)

Checks if two values are equal. Both == and = are supported.

Numbers are compared by value across numeric types, so {{(Id = 5)}} is True when Id is 5L and {{(Price > 5)}} is True when Price is the decimal 10.5 or JSON 10.5. Inline expressions are stricter than {{#if}} in some cases; see Differences from {{#if}} Conditions.

Template:

Status is active: {{(Status == "active")}}

C# Data:

var data = new Dictionary<string, object>
{
    ["Status"] = "active"
};

JSON Data:

{
  "Status": "active"
}

Output:

Status is active: True

Inequality (!=)

Checks if two values are not equal.

Template:

Status is not pending: {{(Status != "pending")}}

Data:

{
  "Status": "active"
}

Output:

Status is not pending: True

Greater Than (>)

Checks if left value is greater than right value.

Template:

Has items: {{(Count > 0)}}

Data:

{
  "Count": 5
}

Output:

Has items: True

Greater Than or Equal (>=)

Checks if left value is greater than or equal to right value.

Template:

Is adult: {{(Age >= 18)}}

Data:

{
  "Age": 18
}

Output:

Is adult: True

Less Than (<)

Checks if left value is less than right value.

Template:

Under budget: {{(Spent < Budget)}}

C# Data:

var data = new Dictionary<string, object>
{
    ["Spent"] = 800,
    ["Budget"] = 1000
};

JSON Data:

{
  "Spent": 800,
  "Budget": 1000
}

Output:

Under budget: True

Less Than or Equal (<=)

Checks if left value is less than or equal to right value.

Template:

At or under limit: {{(Count <= MaxCount)}}

Data:

{
  "Count": 10,
  "MaxCount": 10
}

Output:

At or under limit: True

Membership, String, and Existence Operators

Membership (in)

Checks whether a value is a member of a collection. The right side can be a collection variable, a list literal in parentheses, or a comma-separated string.

Template:

Recognized role: {{(Role in Roles)}}
Can publish: {{(Role in ("Admin", "Editor")):yesno}}

Data:

{
  "Role": "Editor",
  "Roles": ["Admin", "Editor", "Viewer"]
}

Output:

Recognized role: True
Can publish: Yes

Negate with not: {{(not Role in Roles)}}.

String Checks (contains, startswith, endswith)

Template:

Priority: {{(Description contains "urgent"):yesno}}

Data:

{
  "Description": "This order is urgent"
}

Output:

Priority: Yes

These comparisons are case-sensitive, like = and ==.

When the left side is a list, contains checks membership ({{(Tags contains "urgent")}} is True when Tags is ["urgent", "new"]), exactly like "urgent" in Tags. startswith / endswith on a list are always False.

Existence (exists, is empty, is not empty)

exists checks that a variable is present regardless of its value; is empty / is not empty check whether a value is missing, null, blank, or an empty collection.

Template:

Notes provided: {{(Notes exists):yesno}}
Notes empty: {{(Notes is empty):yesno}}

Data:

{
  "Notes": ""
}

Output:

Notes provided: Yes
Notes empty: Yes

Reserved Words and Literal Quoting

and, or, not, true, false and null are always keywords. The operator words added in 1.7.0 (in, contains, startswith, endswith, exists, is, empty) are keywords only where an operator is expected; where a value is expected they are read as variable names, so a variable named Exists or Empty keeps working.

To refer to a variable whose name is a keyword, put it in square brackets. This works for every keyword:

{{([Empty] = "yes")}}
{{(not [Not])}}

Unquoted words fall back to text. In a comparison, a word that is not a variable in your data is compared as its own text: {{(Status = Active)}} works like {{(Status = "Active")}} as long as there is no variable named Active. Both sides are resolved this way, so in {{(A = B)}} both A and B are looked up in your data. Quote a side ({{(A = "B")}}) when you mean the literal text. Always quote text values: an unquoted word silently changes meaning when a variable with that name is added to the data, and a misspelled variable name is compared as text instead of being reported.

Quotes: use double quotes ("text") or, in inline expressions only, single quotes ('text'). Inside a quoted value, \" is a quote and \\ a backslash. Word's typographic quotes (“…”, ‘…’) are converted to straight quotes automatically.

Differences from {{#if}} Conditions

Inline {{(...)}} expressions support the same operators and precedence as {{#if}}, but evaluate some values differently:

{{#if ...}} {{(...)}}
A value used on its own ({{#if Count}} / {{(Count)}}) Truthy unless null, false, empty text, "false", "0", numeric zero, NaN or an empty list Only the boolean true is true; numbers and text (even "true") are False
= with a number and numeric text (Age = 25 with "Age": "25") True (compared as text) False (a number never equals text)
= with booleans and text (IsActive = "True") True (case-insensitive) False
>, <, >=, <= with numeric text ("25" > 18) Numeric text is read as a number Numbers compare as numbers; texts compare with each other; a number and text are not comparable (false)
Single-quoted text ('Active') Not a string A string

Numbers of different types (5, 5L, 5.0m, JSON numbers) compare by value in both. in, contains, startswith, endswith, exists and is empty behave the same in both.

Combining with Format Specifiers

The real power comes from combining expressions with format specifiers for human-readable output.

Basic Combination

Template:

Eligible: {{(Age >= 18):yesno}}

Data:

{
  "Age": 20
}

Output:

Eligible: Yes

Complex Expression with Checkbox

Template:

Access: {{(IsActive and IsVerified):checkbox}}

Data:

{
  "IsActive": true,
  "IsVerified": true
}

Output:

Access: ☑

Multiple Conditions with Checkmark

Template:

Qualified: {{(Age >= 18 and HasLicense):checkmark}}

C# Data:

var data = new Dictionary<string, object>
{
    ["Age"] = 25,
    ["HasLicense"] = true
};

JSON Data:

{
  "Age": 25,
  "HasLicense": true
}

Output:

Qualified: ✓

OR Logic with Format

Template:

Can edit: {{(IsOwner or IsAdmin):enabled}}

Data:

{
  "IsOwner": false,
  "IsAdmin": true
}

Output:

Can edit: Enabled

Advanced Usage

Nested Expressions

Use nested parentheses to control evaluation order.

Template:

Result: {{((var1 or var2) and var3)}}

C# Data:

var data = new Dictionary<string, object>
{
    ["var1"] = true,
    ["var2"] = false,
    ["var3"] = true
};

JSON Data:

{
  "var1": true,
  "var2": false,
  "var3": true
}

Output:

Result: True

Evaluation: 1. (var1 or var2) → (true or false) → true 2. (true and var3) → (true and true) → true

Complex Nested Expressions

Template:

Approved: {{((Age >= 18) and (HasLicense or HasPermit)):yesno}}

Data:

{
  "Age": 20,
  "HasLicense": false,
  "HasPermit": true
}

Output:

Approved: Yes

With Nested Properties

Template:

User active: {{(User.IsActive):checkbox}}
Profile complete: {{(User.Profile.IsComplete):yesno}}

C# Data:

var data = new Dictionary<string, object>
{
    ["User"] = new
    {
        IsActive = true,
        Profile = new
        {
            IsComplete = false
        }
    }
};

JSON Data:

{
  "User": {
    "IsActive": true,
    "Profile": {
      "IsComplete": false
    }
  }
}

Output:

User active: ☑
Profile complete: No

In Loops

Use expressions within loop bodies for dynamic row-level logic.

Template:

{{#foreach Employees}}
- {{Name}}: {{(IsActive and HasCompletedTraining):checkbox}}
{{/foreach}}

C# Data:

var data = new Dictionary<string, object>
{
    ["Employees"] = new[]
    {
        new { Name = "Alice", IsActive = true, HasCompletedTraining = true },
        new { Name = "Bob", IsActive = true, HasCompletedTraining = false },
        new { Name = "Carol", IsActive = false, HasCompletedTraining = true }
    }
};

JSON Data:

{
  "Employees": [
    { "Name": "Alice", "IsActive": true, "HasCompletedTraining": true },
    { "Name": "Bob", "IsActive": true, "HasCompletedTraining": false },
    { "Name": "Carol", "IsActive": false, "HasCompletedTraining": true }
  ]
}

Output:

- Alice: ☑
- Bob: ☐
- Carol: ☐

Comparing Numbers from Different Sources

Template:

Over threshold: {{(CurrentValue > Threshold):yesno}}
Within budget: {{(Spent <= Budget):yesno}}

Data:

{
  "CurrentValue": 150,
  "Threshold": 100,
  "Spent": 800,
  "Budget": 1000
}

Output:

Over threshold: Yes
Within budget: Yes

String Comparisons

Template:

Is pending: {{(Status == "pending"):yesno}}
Not cancelled: {{(Status != "cancelled"):checkmark}}

Data:

{
  "Status": "pending"
}

Output:

Is pending: Yes
Not cancelled: ✓

Best Practices

1. Use Expressions for Simple Logic

For inline boolean checks, expressions are cleaner than separate conditionals.

Good (concise):

Status: {{(IsActive and IsVerified):checkbox}}

Less Good (verbose):

{{#if IsActive}}
{{#if IsVerified}}
Status: ☑
{{/if}}
{{/if}}
{{#if (not IsActive)}}
Status: ☐
{{/if}}
{{#if (not IsVerified)}}
Status: ☐
{{/if}}

2. Use Conditionals for Complex Content

When you need to show/hide entire blocks of content, use {{#if}} instead.

Good:

{{#if IsActive}}
Account Details:
- Name: {{Name}}
- Email: {{Email}}
- Joined: {{JoinDate}}
{{/if}}

Not Recommended:

Account Details: {{(IsActive):yesno}}

3. Keep Expressions Readable

Break complex logic into multiple lines or use separate variables.

Good:

Qualified: {{(Age >= 18 and HasLicense):yesno}}

Less Readable:

Status: {{(((Age >= 18) and (HasLicense or HasPermit)) and (not IsBlacklisted) and (CreditScore > 600)):checkmark}}

Better Approach:

// In your C# code:
var data = new Dictionary<string, object>
{
    ["Age"] = 25,
    ["HasLicense"] = true,
    ["HasPermit"] = false,
    ["IsBlacklisted"] = false,
    ["CreditScore"] = 650,
    ["IsQualified"] = (25 >= 18) && (true || false) && !false && (650 > 600)
};

Template:

Qualified: {{IsQualified:checkmark}}

4. Use Meaningful Variable Names

Clear variable names make expressions self-documenting.

Good:

Eligible: {{(HasValidLicense and IsInsured):yesno}}

Less Clear:

Eligible: {{(var1 and var2):yesno}}

5. Consider Evaluation Cost

Complex expressions are evaluated every time they're encountered. For frequently used values, pre-calculate in your data.

Efficient:

var data = new Dictionary<string, object>
{
    ["IsEligible"] = (age >= 18 && hasLicense)
};

Template:

Eligible: {{IsEligible:yesno}}

Real-World Examples

Approval Workflow Document

Template:

Approval Request
================

Request Details:
- Submitted by: {{Requester.Name}}
- Amount: ${{Amount}}
- Date: {{SubmittedDate}}

Approval Status:
- Manager Approval: {{(ManagerApproved):checkbox}}
- Director Approval: {{(DirectorApproved):checkbox}}
- CFO Approval Required: {{(Amount > 10000):yesno}}
- CFO Approved: {{(CFOApproved or Amount <= 10000):checkbox}}

Final Status: {{((ManagerApproved and DirectorApproved) and (CFOApproved or Amount <= 10000)):checkmark}}

C# Data:

var data = new Dictionary<string, object>
{
    ["Requester"] = new { Name = "John Smith" },
    ["Amount"] = 15000,
    ["SubmittedDate"] = "2025-01-15",
    ["ManagerApproved"] = true,
    ["DirectorApproved"] = true,
    ["CFOApproved"] = true
};

JSON Data:

{
  "Requester": { "Name": "John Smith" },
  "Amount": 15000,
  "SubmittedDate": "2025-01-15",
  "ManagerApproved": true,
  "DirectorApproved": true,
  "CFOApproved": true
}

Employee Benefits Eligibility

Template:

Benefits Eligibility Report
===========================

Employee: {{Employee.Name}}
Hire Date: {{Employee.HireDate}}
Employment Status: {{Employee.Status}}

Eligibility:
- Health Insurance: {{(Employee.Status == "Full-Time" and Employee.TenureMonths >= 1):yesno}}
- 401(k) Match: {{(Employee.Status == "Full-Time" and Employee.TenureMonths >= 6):yesno}}
- Stock Options: {{(Employee.Status == "Full-Time" and Employee.TenureMonths >= 12):yesno}}
- Gym Membership: {{(Employee.Status != "Contractor"):yesno}}

Data:

{
  "Employee": {
    "Name": "Sarah Johnson",
    "HireDate": "2024-08-15",
    "Status": "Full-Time",
    "TenureMonths": 5
  }
}

System Requirements Check

Template:

System Requirements Check
=========================

{{#foreach Requirements}}
{{Name}}: {{(IsMet):checkmark}}
{{/foreach}}

Overall Status: {{((CPUMeetsReq and MemoryMeetsReq) and DiskMeetsReq):active}}
Installation Ready: {{(((CPUMeetsReq and MemoryMeetsReq) and DiskMeetsReq) and OSSupported):yesno}}

C# Data:

var data = new Dictionary<string, object>
{
    ["Requirements"] = new[]
    {
        new { Name = "CPU: 2GHz or faster", IsMet = true },
        new { Name = "RAM: 8GB minimum", IsMet = true },
        new { Name = "Disk: 50GB free space", IsMet = false },
        new { Name = "OS: Windows 10 or later", IsMet = true }
    },
    ["CPUMeetsReq"] = true,
    ["MemoryMeetsReq"] = true,
    ["DiskMeetsReq"] = false,
    ["OSSupported"] = true
};

JSON Data:

{
  "Requirements": [
    { "Name": "CPU: 2GHz or faster", "IsMet": true },
    { "Name": "RAM: 8GB minimum", "IsMet": true },
    { "Name": "Disk: 50GB free space", "IsMet": false },
    { "Name": "OS: Windows 10 or later", "IsMet": true }
  ],
  "CPUMeetsReq": true,
  "MemoryMeetsReq": true,
  "DiskMeetsReq": false,
  "OSSupported": true
}

Project Risk Assessment

Template:

Project Risk Assessment
=======================

Project: {{Project.Name}}

Risk Indicators:
- Behind Schedule: {{(Project.DaysOverdue > 0):yesno}}
- Over Budget: {{(Project.SpentPercentage > 100):yesno}}
- Low Team Morale: {{(Project.MoraleScore < 3):yesno}}
- High Complexity: {{(Project.ComplexityScore >= 8):yesno}}

Risk Level:
- Low Risk: {{((Project.DaysOverdue <= 0) and (Project.SpentPercentage <= 100)):checkbox}}
- Medium Risk: {{((Project.DaysOverdue > 0 or Project.SpentPercentage > 100) and Project.MoraleScore >= 3):checkbox}}
- High Risk: {{((Project.DaysOverdue > 5 or Project.SpentPercentage > 120) or Project.MoraleScore < 3):checkbox}}

Data:

{
  "Project": {
    "Name": "Website Redesign",
    "DaysOverdue": 3,
    "SpentPercentage": 95,
    "MoraleScore": 4,
    "ComplexityScore": 7
  }
}

Operator Precedence

From loosest to tightest binding (the same for {{#if}} and inline expressions):

  1. OR or - loosest
  2. AND and
  3. NOT not
  4. Comparison >, >=, <, <=, =, ==, !=, in, contains, startswith, endswith
  5. Postfix exists, is empty, is not empty - tightest; they attach directly to the value before them (e.g. Notes is empty)

Parentheses () group explicitly and override all of the above. and always binds tighter than or, and a comparison is evaluated before not is applied to it.

Examples

Expression: var1 or var2 and var3 Evaluation: var1 or (var2 and var3) ← AND binds tighter

Expression: (var1 or var2) and var3 Evaluation: Parentheses force OR first

Expression: not var1 and var2 Evaluation: (not var1) and var2 ← NOT binds tighter than AND

Expression: not Status = "Active" Evaluation: not (Status = "Active") ← the comparison is evaluated first

Summary

Boolean expressions enable powerful inline logic in your templates:

  • ✅ Logical operators: and, or, not
  • ✅ Comparison operators: =, ==, !=, >, >=, <, <=
  • ✅ Membership, string, and existence operators: in, contains, startswith, endswith, exists, is empty, is not empty
  • ✅ Nested expressions with parentheses
  • ✅ Combine with format specifiers for readable output
  • ✅ Works with nested properties and arrays
  • ✅ Use in loops and conditionals
  • ✅ Same data works with C# Dictionaries or JSON
  • ✅ Case-insensitive operators

For more information, see: - Format Specifiers Guide - Format boolean output - API Reference - Complete API documentation - Examples - More code examples - FAQ - Common questions