Debugging and Writing Expressions

Edited

This guide explains how to write expressions for custom properties, how to use the Evaluation Trace to debug them, and how different field types (text, number, and boolean) behave inside an expression.


Anatomy of an Expression

An expression is a formula that calculates a value automatically from other fields on the record. Here is an example that produces a weighted score:

([ROI / Payback Period-Value] * .30) + ([Customer Experience-Value] * .25)
  + ([Scalability-Value] * .25) + ([Implementation Complexity-Value] * .40) + 3 + 2

Breaking It Down

Component

What It Does

[ROI / Payback Period-Value]

A field reference — the square brackets pull in the current value of that field from the record.

* .30

A multiplication operator followed by a numeric literal. Multiplies the field value by 0.30 (30 %).

( ... )

Parentheses group a sub-calculation so it is evaluated first.

+

The addition operator adds the results together.

3 + 2

Plain numeric literals added to the total.

When the expression above is evaluated against a record where:

  • [ROI / Payback Period-Value]4

  • [Customer Experience-Value]2

  • [Scalability-Value]1

  • [Implementation Complexity-Value]2

the result is 7.75.


The Evaluation Trace

When you click Evaluate in the expression editor, a result is displayed. The Evaluation Trace expands below and walks you through every step the engine took to reach that result. Click Show trace to reveal it, and Hide trace to collapse.

The trace has four sections:

1. Field Values

Lists every field reference found in the expression together with its current value from the loaded sample record.

[ROI / Payback Period-Value]       ⇒ 4
[Customer Experience-Value]        ⇒ 2
[Scalability-Value]                ⇒ 1
[Implementation Complexity-Value]  ⇒ 2

Tip: If a field shows (null) here, the record does not have a value for that field. Null fields can cause unexpected results — see Handling NULL Values below.

2. Substituted Expression

The engine replaces each field reference with its resolved value so you can see the raw calculation:

(4 * .30) + (2 * .25) + (1 * .25) + (2 * .40) + 3 + 2

This is helpful for spotting when a field resolved to an unexpected value.

3. Sub-Expression Results

Each parenthesised group is evaluated individually:

(4 * .30)  ⇒ 1.2
(2 * .25)  ⇒ 0.5
(1 * .25)  ⇒ 0.25
(2 * .40)  ⇒ 0.8

If one sub-expression produces an unexpected number, focus your debugging there.

4. Final Result

The fully calculated answer:

Result ⇒ 7.75

Using Sample Fields

Before you can evaluate an expression you need a sample context — a real or representative set of field values to test against.

  1. Automatic loading — When you enable expressions on a custom property and begin editing, the editor automatically loads a sample context from the current entity type (Project, Action, or Person). The Fields dropdown in the editor footer lists every available field name and its sample value.

  2. Inserting fields — Click the Fields button to browse available field names. Selecting a field inserts it into the expression wrapped in square brackets, e.g. [Priority].

  3. Checking field validity — The editor highlights any field token that does not exist in the loaded context. If you see a red "Invalid" badge next to a token, the field name is misspelled or is not available for this entity type.

  4. Reviewing values — Open the Fields dropdown to see the sample value next to each field name. This tells you whether the field is a number, a piece of text, a boolean, or null.


Field Types and How They Behave

Number Fields

Number fields (e.g. Score & Number properties) resolve to a numeric value in the expression context.

  • You can use arithmetic operators directly: [Score] * 2, [Budget] + 1000.

  • Division by zero will produce an evaluation error.

  • If a number field is empty (null), arithmetic with it returns null. Guard against this with COALESCE([Score], 0).

Text (String) Fields

Text fields (e.g. Status, Category, an Option's display text) resolve to a string value.

  • Compare with quotes: [Status] = "Active".

  • String comparison is case-insensitive.

  • The + operator on two strings performs concatenation: "Phase: " + [Phase] produces Phase: Build.

Boolean (Yes/No) Fields

Yes/No fields resolve to true or false.

  • Use them directly in conditions: IF([IsActive], "Yes", "No").

  • Combine with AND / OR: [IsActive] AND [HasBudget].

  • The values FALSE, 0, "" (empty string), NULL, and the literal string "false" are all treated as falsy. Everything else is truthy.

  • Be aware that the string "false" is a special case — even though it is a non-empty string, the engine treats it as falsy to align with the Yes/No data type.

Date and DateTime Fields

  • Dates are compared chronologically: [DueDate] < [StartDate].

  • Use the DIFF function to get the difference between two dates: DIFF([StartDate], [EndDate], "day").


What Happens When You Mix Types

Using a Text value in a Numeric operation

If a field is a text (string) type and you attempt to use it in an arithmetic operation such as multiplication or subtraction, the expression engine will try to coerce the string into a number.

Scenario

Example

Result

String contains a valid number

"42" * 2

84 — the string is automatically converted.

String is not a number

"High" * 2

Error — the engine cannot convert "High" to a number and the expression fails.

String is empty or null

"" + 5

The + operator treats this as string concatenation, producing "5" as text instead of the number 5.

Validation warning: When you save a property definition, the editor validates the expression by substituting placeholder values for each referenced field. If a text field is used in a position where a number is expected, the validation will flag this as an error. Fix it by either:

  • Changing the referenced field to a Number data type, or

  • Wrapping the field in a guard: IF([TextField] = NULL, 0, [TextField]) so it always resolves to a number.

Using a Number value in a String operation

When you use + and one side is a string, the engine converts the number to text and concatenates:

"Score=" + [Score]

If [Score] is 85, the result is the string "Score=85".


Common Debugging Tips

  1. Always click Evaluate after writing or changing an expression. The trace immediately shows whether field values are what you expect.

  2. Check the Substituted Expression to see the raw calculation. If a field resolved to null or an unexpected string, that is the source of the problem.

  3. Look at Sub-Expression Results to isolate which group is producing the wrong number.

  4. Watch for type mismatches. A field you think is numeric might actually be stored as text (e.g. an option value). The trace will show its resolved type.

  5. Use COALESCE for safety. Wrap any field that could be empty: COALESCE([OptionalScore], 0) ensures a zero instead of null.

  6. Use IF for branching. Guard expressions with IF([Field] = NULL, <default>, <calculation>) to avoid errors on incomplete records.

  7. Check for invalid tokens. If a field name badge appears in red below the expression, the name does not match any available field. Check spelling, spacing, and capitalisation.


Quick Reference

Operators

Operator

Description

+

Addition (numbers) or concatenation (strings)

-

Subtraction

*

Multiplication

/

Division

%

Modulo (remainder)

=

Equals

<>

Not equals

< <= > >=

Comparisons

AND

Both sides must be true

OR

At least one side must be true

NOT

Negates a condition

Functions

Function

Syntax

Description

IF

IF(condition, trueValue, falseValue)

Returns one of two values based on a condition.

CASE

CASE(test1, result1, ..., default)

Evaluates conditions in order; returns the first match or the default.

COALESCE

COALESCE(value1, value2, ...)

Returns the first non-null value.

ABS

ABS(value)

Absolute value of a number.

DIFF

DIFF(date1, date2, "unit")

Difference between two dates in the specified unit (day, month, year).

CONTAINS

CONTAINS(text, search)

True if the text contains the search string.

STARTSWITH

STARTSWITH(text, prefix)

True if the text starts with the prefix.

ENDSWITH

ENDSWITH(text, suffix)

True if the text ends with the suffix.

Truthiness Rules

Value

Treated as

FALSE, 0, "", NULL, "false"

Falsy

Any non-zero number, non-empty string (except "false"), TRUE

Truthy


Worked Example

Goal: Calculate a weighted priority score from four rating fields and add a fixed baseline of 5.

Expression:

([ROI / Payback Period-Value] * .30) + ([Customer Experience-Value] * .25)
  + ([Scalability-Value] * .25) + ([Implementation Complexity-Value] * .40) + 3 + 2

Trace output with sample values:

Step

Detail

Field Values

ROI / Payback Period-Value → 4, Customer Experience-Value → 2, Scalability-Value → 1, Implementation Complexity-Value → 2

Substituted Expression

(4 * .30) + (2 * .25) + (1 * .25) + (2 * .40) + 3 + 2

Sub-Expression Results

(4 * .30) → 1.2, (2 * .25) → 0.5, (1 * .25) → 0.25, (2 * .40) → 0.8

Final Result

1.2 + 0.5 + 0.25 + 0.8 + 3 + 2 = 7.75

If any of these ratings were empty, wrapping them in COALESCE would prevent a null result:

(COALESCE([ROI / Payback Period-Value], 0) * .30)
  + (COALESCE([Customer Experience-Value], 0) * .25)
  + (COALESCE([Scalability-Value], 0) * .25)
  + (COALESCE([Implementation Complexity-Value], 0) * .40) + 3 + 2

Was this article helpful?

Sorry about that! Care to tell us more?

Thanks for the feedback!

There was an issue submitting your feedback
Please check your connection and try again.