Skip to contentSkip to navigationSkip to topbar
Page tools
Useful for sharing or LLM
Accelerate development with AI

On this page
Looking for more inspiration?Visit the

Type matching


Supported data types

supported-data-types page anchor

Twilio Cohorts filter criteria accept the following primitive types:

TypeDescriptionExample
stringText value"hello", "US"
integerWhole number (64-bit signed)0, 25, -1
doubleFloating-point number (64-bit)3.14, -0.5
booleanBoolean valuetrue, false
timestampDate-time valuetimestamp("2026-01-01T00:00:00Z")
durationTime durationduration("24h"), duration("30m")

Operands must match type

operands-must-match-type page anchor

When you combine two values in filter criteria, both sides generally need to be the same type.

Comparison operators

comparison-operators page anchor
OperatorAllowed operand types
==, !=Any type, provided that both operands are the same type — except for integer and double, which are numerically coerced when compared.
<, <=, >, >=integer, double, string, timestamp, or duration. Both operands must be the same type — except for integer and double, which are numerically coerced when compared.
profile.trait.Account.age > 18

age and 18 are both numbers, so this comparison is valid. Comparing a string-typed trait to an integer literal, or a timestamp to a string, is invalid and causes an evaluation failure.

OperatorAllowed operand types
+integer or double + integer or double, string + string (concatenation), or timestamp + duration.
-integer or double - integer or double, timestamp - duration, or timestamp - timestamp.
*, /integer or double, combined with integer or double.
%integer % integer only.

integer and double values can be mixed in +, -, *, and /, but not in %.

Mixed types, such as a string plus an integer, are invalid.

The left-hand value's type must match the type of the elements in the list on the right.

profile.trait.Contact.country in ["US", "FR", "DE"]

Comparing a string-typed trait to a list of integers is invalid. Unlike the == operator, this comparison does not coerce integer or double values. Ensure that each element in the list shares the trait's data type, or explicitly cast the elements. For example, a trait of type double is compatible with [1.0, 2.0] or [double(1), double(2)].

Conditional (ternary)

conditional-ternary page anchor

Both branches of a ?: expression must evaluate to the same type.

profile.trait.Metrics.score > 0 ? profile.trait.Metrics.score : 0.0

In this example, score is a double, so the value in the right-hand branch must also be a double. Like the in operator, the conditional operator does not coerce integer and double values. Make sure that the expressions in both branches share the same data type, or cast one branch explicitly.


Type mismatches are rejected, not silently excluded

type-mismatches-are-rejected-not-silently-excluded page anchor

A type mismatch behaves differently from a missing trait. A missing trait evaluates to false and quietly excludes the profile. A type mismatch fails validation when you create or update a cohort or create a snapshot from inline criteria. In those cases, the request is rejected with an HTTP 400 Bad Request error.


Reconciling mismatched types

reconciling-mismatched-types page anchor

If you need to combine or compare values of different types, convert one of them explicitly by using a type-conversion function such as string(), int(), or double().

string(profile.trait.Account.age) == "18"

If a trait is stored as a string but represents a date (for example, 2026-01-01T00:00:00Z), convert it to a timestamp before you compare it with now() or with another timestamp. The string must use RFC 3339 format.

timestamp(profile.trait.Account.lastPurchaseDate) > timestamp("2026-01-01T00:00:00Z")

If the trait is already of type timestamp, compare it directly without wrapping it in timestamp().


Numeric and timestamp behavior

numeric-and-timestamp-behavior page anchor

Some numeric and timestamp operations behave differently from the standard CEL specification.

Converting numbers to strings

converting-numbers-to-strings page anchor

When you convert a double to a string with string(), whole numbers keep a decimal point, so string(1.0) returns "1.0" rather than "1". Very large and very small numbers use scientific notation, such as "1.0E7" for 10000000.0.

Because the text format of a number can vary, don't compare the result of string() on a number to a string literal. Compare the number itself instead.

profile.trait.Game.score == 1

The same applies to math.least() and math.greatest() when you give them a mix of integers and doubles. The result is a double, so string(math.least(1, 2.5)) returns "1.0".

string() on integers, booleans, and strings works as the CEL specification describes.

Comparing large integers

comparing-large-integers page anchor

When you compare a double trait to an integer literal, the literal is converted to a double before the comparison. A double can't represent every integer above 253 (9,007,199,254,740,992), so neighboring integers beyond that point can compare as equal. For example, this expression is true:

9007199254740992.0 == 9007199254740993
(warning)

Avoid large integer literals

Don't compare double traits to integer literals larger than 253 (about 9 quadrillion). The results might include or skip profiles you don't expect.

Timestamp precision and format

timestamp-precision-and-format page anchor

The timestamp() function keeps millisecond precision. Digits beyond the third fractional digit are dropped, so two timestamps that differ only below the millisecond are treated as equal.

When you convert a timestamp to a string with string(), the result is in UTC with a trailing Z. Trailing zeros are removed from the fractional seconds, and the fraction is omitted when it's zero. For example, string(timestamp("2024-01-01T10:00:00+02:00")) returns "2024-01-01T08:00:00Z".


  • Review the available functions, including type conversion.
  • Learn about null handling for missing traits.