Set Property Value expressions

An expression is a small formula that TrialGrid works out when the Action runs. It always sets the property: whatever the expression produces is written, and if the expression cannot be worked out — for example arithmetic on a property that is blank — the property is cleared and a warning is recorded against the object, so a broken expression is never silent.

Reference, Set and User properties cannot be the target of an expression. See Operations for why a User property cannot be set by an Action at all.

The following functions and values are available.

Written as

Meaning

today()

The current date (server time). Use it in a Date expression.

now()

The current date and time (UTC). Use it in a DateTime expression.

days(n), weeks(n)

A span of whole days or weeks, added to or subtracted from a date or datetime.

hours(n), minutes(n), seconds(n)

A span of time. Only meaningful with a datetime — added to a date they have no effect (a date counts whole days only).

months(n), years(n)

A span of whole months or years. These clamp to a real date: months(1) added to 31 January gives 28 February, not an impossible 31 February. For an exact number of days use days(28) rather than months(1).

to_date(x)

Converts a value to a date. Applied to a datetime it gives that datetime's calendar day in UTC, so to_date(object.attribute("Collected At")) extracts the day from a date-and-time property. It also parses a plain "2026-07-21" string. A blank value stays blank (clears the property).

to_datetime(x)

Converts a value to a date and time, always in UTC. A plain date becomes midnight UTC; a string carrying an offset or a Z is converted to UTC. A blank value stays blank (clears the property).

str(x)

Converts a value to text, so a date, time or number can be joined into a text property — e.g. object.attribute("Audit") + " at " + str(now()). A blank value becomes an empty string.

user

The person running the Action. Write it on its own to set a User property to them, or use user.username, user.email or user.full_name to put their details into a text property. full_name falls back to the username when no name is set.

Setting a User property to someone who does not work in this Organization is refused when the Action runs. As with any expression that cannot produce a value, the property is emptied and a warning is shown against the object, so check the warnings after a run.

object.attribute("Name")

The value of another of this object's custom properties, named exactly as it appears. A Reference property returns the referenced object's id, and a User property returns the chosen person's user id, not their name.

object.has_label("Name")

True when this object carries the named label, False otherwise (the name is matched without regard to case). Use it in a condition, e.g. "Locked" if object.has_label("Locked") else "Open". The label must be one defined in this URL.

default(value, fallback, ...)

The first argument that is not blank. Useful for seeding, e.g. default(object.attribute("Version"), 0) + 1. More fallbacks can be chained: default(object.attribute("Preferred"), object.attribute("Backup"), "None").

Blank values and conditions

A blank Date or Number has no value — it is not treated as zero or as any particular date — so doing arithmetic on a blank (object.attribute("Start Date") + days(10) when Start Date is empty) is an error, and the property is cleared with a warning. To guard against a blank, use a conditional so the arithmetic only runs when there is a value:

object.attribute("Start Date") + days(10) if object.attribute("Start Date") else today()

The if ... else ... conditional is the way to guard arithmetic. default() only chooses between plain values — default(object.attribute("X") + days(10), today()) still fails on a blank X, because the arithmetic is worked out before default chooses.

You can compare and combine values: == and in compare text without regard to case (object.attribute("Status") == "open" matches a stored Open), and + joins text (object.attribute("Audit") + "\n" + user.full_name appends a line to a long-text property).

Dates and times

All dates and times are in UTC. now(), today(), and every stored date/datetime are UTC, so a date never shifts under a timezone.

A Date property expects an actual date and a DateTime property an actual date-and-time — the expression result is not guessed into shape. today() + days(10) and object.attribute("Enrolment Date") + weeks(6) produce a date, so they write straight into a Date property; a datetime written into a Date property (or a plain "2026-07-21" string) is refused rather than silently trimmed. When you need to change one into the other, convert it explicitly:

  • to_date(x) gives the date. On a datetime it takes the UTC calendar day, so to_date(object.attribute("Collected At")) writes the day a date-and-time was collected into a Date property. A datetime near midnight lands on the correct UTC day rather than the local one.

  • to_datetime(x) gives a UTC date-and-time. A plain date becomes midnight UTC.

A blank input to either stays blank, so to_date(object.attribute("Collected At")) clears the target when nothing was collected rather than erroring.

Numbers and precision

A numeric result must fit the property's configured number of decimal places — it is not silently rounded. A result of 2.6 written to a property that allows no decimal places clears the property and records a warning rather than storing 3, so a value is never quietly changed. If you want rounding, make the expression produce a value that already fits (for example keep the property's decimal places, or compute a whole number). Trailing zeros are not precision, so 2.00 still fits a whole-number property.

Working with a property value

Most of the time you use object.attribute("Name") directly — in arithmetic, a comparison, or a concatenation — and it behaves as the right kind of value automatically. When you need to be explicit, the value offers these:

Written as

Result

object.attribute("Name").raw_value()

The value exactly as stored, as text, with no conversion — a number stays "5.0", a date stays "2026-07-21". Use this when you want the stored text rather than a typed value.

object.attribute("Name").is_empty()

True when the property is unset or blank, False otherwise. The reliable way to test for a blank before doing arithmetic on a Date or Number.

So the blank guard above can also be written to say plainly what it checks:

object.attribute("Start Date") + days(10) if not object.attribute("Start Date").is_empty() else today()

Two things to watch:

  • These are functions — write the parentheses. .is_empty() tests emptiness; .is_empty (no parentheses) is always treated as true, so the check silently never fires.

  • .raw_value() gives a property's own stored text; use str(x) to turn any other value — a date, time or number, such as str(now()) — into text for joining into a text property.

The plain if object.attribute("X") ... short form (without .is_empty()) means "is set" for most properties, but for a Yes/No (Boolean) property it means "is Yes". Use not object.attribute("X").is_empty() when you specifically mean "has a value" on a Boolean.

Renaming or deleting a property an expression reads

A property named in an expression is protected so an expression cannot be orphaned without you knowing:

  • Renaming a property that an expression reads rewrites the expression to the new name automatically, so it keeps working — you do not have to hunt down and edit every expression.

  • Deleting a property that an expression reads is refused: the delete tells you which Actions read it, so you can change or remove those expressions first. (Deleting a property that an operation merely sets is allowed — that operation is simply removed, since it can no longer do anything.)

  • Changing the type of a property an expression reads is refused if the new type would stop the expression evaluating — for example turning a Number that an expression does arithmetic on into Text.

Worked examples

Expression

Result

today() + days(10)

A due date ten days from today.

object.attribute("Enrolment Date") + weeks(6)

Six weeks after an enrolment date held on the object.

object.attribute("Version") + 1

Increments a numeric version each time the Action runs.

to_date(object.attribute("Collected At"))

Copies the UTC day of a date-and-time property into a Date property.

object.attribute("Audit") + user.full_name + " at " + str(now()) + "\n"

Appends a "who and when" line to a text audit property each time the Action runs.

"#ff0000" if object.attribute("Overdue") == "Yes" else "#00ff00"

Red when overdue, green otherwise (into a Color property).

"Locked" if object.has_label("Locked") else "Editable"

Sets a status text from whether the object carries the Locked label.

Testing an expression

The Test button evaluates the expression there and then and shows the result, without saving anything or changing any object. It runs against sample values, not real data: now() and today() are a fixed sample instant, user is a sample user, and each object.attribute("…") is filled with a value of the right type for that property — a number is 1, a date is a sample date, a choice is its first option, and so on.

So Test confirms two things: the expression is valid, and what kind of value it produces (and whether that value fits the property). It does not predict which branch a conditional will take for a real object — because every sample number is 1, object.attribute("A") >= object.attribute("B") is true under Test regardless of what A and B will actually hold. Treat the shown result as "this is a valid value of the right type", not as the value a particular object will get.