This page reflects current behavior. Where a section notes that something isn’t supported, it may change in a future release — the section will say so when that’s the case.
Multi-value and array-shaped values
The most common source of confusion. Two related shapes:- Multi-picklist fields — fields whose type is
MULTI_PICKLIST. A single cell holds several selected options. - Multi-value text columns surfaced through Data Mine — when a Data Mine trigger fires, multi-value text columns in the payload serialize as a JSON array of objects, not as plain text. The shape looks like this:
If you’re working with a multi-picklist field and want the working patterns for iteration, aggregation, and passing selections into an API body, see Multi-value fields. This section covers what specifically breaks inside a calculation.
Aggregating a multi-picklist into a delimited string
Not currently supported. Aggregations over list-typed fields aren’t supported in calculations. What people try:STRING_AGG and STRING_AGG_UNIQUE only accept single-value text supplied via a related-field aggregation — they cannot operate on a MULTI_PICKLIST field directly. There is no calculation function that aggregates the selections of a multi-picklist into a delimited string.
What works instead. Do the aggregation in an Execute Script action and, if the result needs to live on a record, write it back with Update Record Fields. The full pattern (input mapping, iteration, aggregation, and passing the result into a Send API Request body) is on Multi-value fields.
Flattening a Data Mine array payload into plain text
Not currently supported. Calculations cannot convert structures — the[{"value":..., "type":...}, ...] shape that Data Mine produces for multi-value text columns cannot be flattened into plain delimited text inside a calculated column or the field mapping on a Create Record action.
What happens if you try: the destination text field stores the literal JSON, for example:
WD, ZIP users expect.
SPLIT returns an array you can’t index into
SPLIT(text, delimiter) returns an array of substrings. That’s expected — but selecting a single element from that array isn’t supported in calculations. There’s no array indexing syntax ([0], [1], etc.) and no INDEX/NTH function to pick a piece.
Example of the trap:
- If the position is genuinely fixed (for example, every ID is always exactly 4 characters), use
LEFT,RIGHT, orMID. These return text you can use directly. - If the split point is variable (for example, “everything before the dash”), combine
FINDorSEARCHto locate the delimiter withLEFT/MIDto extract the piece you want:
DATEVALUE only accepts year-month-day input
DATEVALUE(text_date) doesn’t auto-detect or parse arbitrary date formats. The input must be in YYYY-MM-DD form — date only, no time component. Other formats won’t parse and the function returns blank, including:
- US-style
MM/DD/YYYYor EuropeanDD/MM/YYYY - Written-out months like
Jun 1, 2026 - ISO 8601 strings with a time component like
2026-06-01 12:34:56or2026-06-01T12:34:56Z
- Normalize date text to
YYYY-MM-DDin the source system or an ingest step before it reaches the calculation. - If the components are available as separate values, skip
DATEVALUEentirely and build the date withDATE(year, month, day). - For text dates with predictable structure, use
LEFT/MID/RIGHTto rearrange the pieces into year-month-day before passing toDATEVALUE.
Regex syntax: Java-style
REGEXEXTRACT, REGEXMATCH, and REGEXREPLACE are fully supported. Standard regex operations — character classes, quantifiers, groups, anchors, alternation, backreferences — work as expected.
REGEXREPLACE replaces all matches of the pattern in the string, not just the first.- Escaping inside string literals — calculation text is enclosed in single quotes, so backslashes in patterns need to be escaped: write
'\\d+'(not'\d+') to match one or more digits. - A few advanced constructs — possessive quantifiers (
*+,++) and certain Unicode property classes behave per the Java spec, not PCRE.
Pattern javadoc is the authoritative syntax reference. When in doubt, test the pattern with a small string against REGEXMATCH before building a larger calculation around it.
Adding or subtracting days from a date
UseDATEADD(unit, value, date). Unit tokens are unquoted, and a negative value subtracts:
DATEDIF against NOW() with the 'D' unit:
IF branches must return the same type
Both branches ofIF(condition, value_if_true, value_if_false) must return the same data type. Mixing types raises a validation error and the calculation won’t save.
A common trip-up is using '' (empty string) as a placeholder in the false branch of an otherwise date-typed expression:
BLANK() instead. It represents the absence of a value without changing the branch’s type, so both sides stay date-typed:
BLANK() (not 0 or '') when one branch has no meaningful value.
Detecting missing values, including the literal text ‘null’
ISBLANK(field) detects when a field has no value at all. Elementum normalizes empty text and true nulls the same way at the field level — ISBLANK returns TRUE for both. In practice you don’t need a separate check for “null vs. blank” on a field.
What ISBLANK does not catch is a text field whose content is the four characters n, u, l, l. This shows up when the field was populated by an upstream system that writes the string null as a marker for missing data — API responses, webhook payloads, and some CSV exports do this. From the calculation’s perspective the field holds a non-empty text value, so ISBLANK returns FALSE.
To detect that literal string, compare against the quoted text:
OR:
null is not a keyword in the calculation language. Writing null bare (unquoted) raises a syntax error like Invalid Syntax Error at line 1, position N: missing ')' at ','. Use BLANK() to return an empty value from a branch of an IF, and 'null' (quoted) to compare against the literal text.Worked example: text-to-date, empty when the source is ‘null’
Convert a text field to a date, and return blank — not today’s date — when the source is empty or contains the stringnull:
- Both branches must be date-typed.
BLANK()on the true branch takes the surrounding branch’s type —''(empty string) is text-typed and would raise a validation error. See IF branches must return the same type. DATEVALUEonly parsesYYYY-MM-DD. If the source date is in another format,DATEVALUEreturns blank silently rather than the value you expect — see DATEVALUE only accepts year-month-day input.IFtakes exactly three arguments — condition, value if true, value if false. Nested checks belong inside the condition (as withORabove), not as extra positional arguments in the outerIF(...).
Inserting text into the middle of a string
There is no dedicated “insert” function. Split the string at the target position withLEFT and RIGHT, then reassemble it with CONCAT:
FIND to locate the anchor dynamically:
SUBSTITUTE(text, old_text, new_text) is a different tool — it replaces every match of old_text. It only produces an “insert” when you replace an anchor with itself plus the new text (for example, SUBSTITUTE(text, 'world', 'world Sorry')), which requires a unique anchor and rewrites every occurrence.
FIND is case-sensitive and returns the position of the first match. If the anchor could appear more than once in the source text, this pattern inserts before the first occurrence only.Worked example: building an HTML email body from records
A common driver for this pattern is assembling an HTML email body from a list of records — the outer template is fixed, and you need to drop<tr> rows into the <tbody> before sending. The template looks like this:
template value holding that markup and a new_rows value holding the row markup to insert — for example:
</tbody> and stitch the rows in front of it:
FIND('</tbody>', template)locates the character position where the closing tag starts.LEFT(template, FIND(...) - 1)returns everything in the template before the closing tag.RIGHT(template, LEN(template) - FIND(...) + 1)returns the closing tag and everything after it, so the tag itself is preserved.CONCAT(...)glues the three pieces back together in order — before, rows, after.
new_rows. Nesting the aggregation and the insertion in a single expression is hard to read and hard to debug.
The same pattern in an Execute Script action
If your automation already uses an Execute Script action for other logic, JavaScript’sString.prototype.replace is shorter and avoids off-by-one arithmetic with character positions. Replace the marker with the new content followed by the marker itself:
.map(...).join('') and insert once:
Cannot read property 'replace' of undefined:
recordsmust be the array of record data, not a single record or an unresolved reference. Access it viainput.parameters— parameter names are not injected as standalone variables in the script scope (see Execute Script).templatemust be a string at the point you call.replace()on it. A field reference that hasn’t loaded yet resolves toundefined.
<tbody> and a summary line before </table>), chain .replace() calls — one per marker.
Concatenating strings
CONCAT(a, b, c, ...) is the function to join text values. The + operator is for numeric addition only — applying it to text fields does not concatenate them.
This is a common surprise for people coming from JavaScript, PHP, or some spreadsheet languages where + doubles as a string-concatenation operator.
CONCAT accepts any number of arguments and converts non-text values (numbers, dates) to text automatically before joining.
Changing a column’s field type
Not supported. A column’s field type cannot be changed in place after the column is created. This applies to all type conversions — single-value text ↔ multi-value text, text ↔ multi-picklist, and so on. Recommended pattern — create a new column of the desired type, then delete the old one. If the old column has data you need to preserve, backfill the new column via an automation or import before deleting the old, and update any dependencies (calculations, automations, filters, layouts) to point at the new column.Error reference
Related documentation
- Calculations — Full function reference
- Tables — Calculated columns on tables
- Data mining — Triggers and payload shapes