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 |
|---|---|
|
The current date (server time). Use it in a Date expression. |
|
The current date and time (UTC). Use it in a DateTime expression. |
|
A span of whole days or weeks, added to or subtracted from a date or datetime. |
|
A span of time. Only meaningful with a datetime — added to a date they have no effect (a date counts whole days only). |
|
A span of whole months or years. These clamp to a real date: |
|
Converts a value to a date. Applied to a datetime it gives that datetime's calendar day in UTC,
so |
|
Converts a value to a date and time, always in UTC. A plain date becomes midnight UTC; a string
carrying an offset or a |
|
Converts a value to text, so a date, time or number can be joined into a text property — e.g.
|
|
The person running the Action. Write it on its own to set a User property to them, or use
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. |
|
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. |
|
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. |
|
The first argument that is not blank. Useful for seeding, e.g.
|
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, soto_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 |
|---|---|
|
The value exactly as stored, as text, with no conversion — a number stays |
|
|
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; usestr(x)to turn any other value — a date, time or number, such asstr(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 |
|---|---|
|
A due date ten days from today. |
|
Six weeks after an enrolment date held on the object. |
|
Increments a numeric version each time the Action runs. |
|
Copies the UTC day of a date-and-time property into a Date property. |
|
Appends a "who and when" line to a text audit property each time the Action runs. |
|
Red when overdue, green otherwise (into a Color property). |
|
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.