MQL has several built-in functions for capabilities that don't neatly fit into concise syntax. Functions can operate on a variable amount of parameters and always evaluate to a value. The basic syntax for functions is <name>(<arg1>, <arg2>, ... <argN>).


Request a function!

Don't see a function you want? Let us know via email or Slack!

String functions


length(input: string) -> integer

Use length to retrieve the number of Unicode code points used to encode a string in UTF-8. Note that this is not equivalent to the number of bytes or glyphs.

length("") -> 19
length("📧") -> 1
length("émaîl") -> 5

like / ilike

like(input: string, pattern: string, ...) -> bool
ilike(input: string, pattern: string, ...) -> bool

like is used to match a string against a list of predefined wildcard patterns. like performs a case-sensitive match against the entire string, but ilike is case-insensitive. Use wildcard characters to represent unknown substrings:

  • * is a placeholder for a string of any length, including empty strings
  • ? is a placeholder for a single character within the string

Remember that like and ilike are evaluated against the entire string. To look for a substring, wrap it in *, such as ilike(body.plain.raw, "*password*").

If input is null, then like and ilike will always return null.


like("[email protected]", "*") -> true

# use ilike for case-insensitive matches
like("[email protected]", "*") -> false
ilike("[email protected]", "*") -> true

# if multiple wildcard patterns are specified, only one needs to match
like("[email protected]", "*@*.org", "*@*.com", "*@*.gov") -> true

# use ? to match exactly 1 unknown character
like("[email protected]", "[email protected]") -> true
like("[email protected]", "[email protected]") -> false

edit_distance / iedit_distance

edit_distance(input1: string, input2: string) -> integer
iedit_distance(input1: string, input2: string) -> integer

edit_distance is used to calculate the Levenshtein edit distance between two strings. The returned value is equal to the minimum number of character operations (deletion, insertion, substitution) required to transform one string into the other. edit_distance finds the case-sensitive distance between the two strings, but iedit_distance is case-insensitive.

edit_distance('Bat', 'bot') -> 2
iedit_distance('Bat', 'bot') -> 1

Regular expressions

Both regular expression functions below currently use RE2 for the regular expression syntax. RE2 shares many features with PCRE, but is typically more efficient and does not include computationally expensive features of PCRE. For more details, see the official documentation for RE2.

Regular expression tester

Single quote ' vs double quote "

Use single quotes for regular expressions so that escape sequences such as \d don't have to be escaped twice.

For instance, to find three consecutive digits anywhere in a string, use regex_search(field, '\d\d\d') to avoid double escapes of the \\. This is equivalent to regex_search(field, "\\d\\d\\d")

regex_match / iregex_match

regex_match(input: string, pattern: string, ...) -> bool
iregex_match(input: string, pattern: string, ...) -> bool

regex_match is used to match an entire string against a list of regular expressions. If at least one of the regular expressions matches the entire string, then regex_match will return true. regex_match performs a case-sensitive regular expression match, but iregex_match is case-insensitive. Both functions match against the entire string, so add leading and trailing wildcards (.* or .*?) to search for a substring within the entire input string.

If input is null, then regex and iregex will always return null.


regex_match("[email protected]", ".*[email protected]") -> true

# use iregex for case-insensitive matches
regex_match("[email protected]", "") -> false
iregex_match("[email protected]", "") -> true

# if multiple wildcard patterns are specified, only one needs to match
regex_match("[email protected]", "@.*.org$", "@.*.com$", "@.*.gov$") -> true

regex_search / iregex_search

regex_search(input: string, pattern: string, ...) -> bool
iregex_search(input: string, pattern: string, ...) -> bool

regex_search is used to check if a string contains a match against a list of regular expressions. Unlike regex_match, the full string does not need to match. regex_search(field, '\bfoo\b') has the same behavior as regex_match(field, '.*\bfoo\b.*').

For case-insensitivity regular expression matching, use iregex_search.


Unicode and strings

Characters in strings are a complicated matter. Within MQL, all strings are always valid unicode and internally encoded as UTF-8.

Functions within MQL typically operate on unicode strings, and a "character" means a single UTF-8 code point, which can span more than one byte and may be an incomplete glyph.

Array functions

Arrays could come from various places: on the MDM, such as or attachments; inline with [...] syntax, such as [body.html.raw, body.plain.raw]; or returned from functions, such as beta.binexplode.

To process an array and loop over elements, MQL uses a few builtin functions. Array functions loop over an input array with an expression and return another array or some other value. Array functions typically take two arguments: the source array, and an expression to evaluate over the elements.

Within the looping expression, use the prefix . to access the loop item. To access an attribute of the looping item, use .<attribute>. To access fields on the root level of the MDM, drop the prefix. You can access a loop item from a parent scope with an additional ., such as ...

For example, to check if an array of strings contains the string secret:

any(["super", "secret", "text"], . == "secret")

To check the body.links array that the attribute .mismatched is always false:

all(body.links, .mismatched == false)

To check if either body.plain.raw or body.html.raw to contain any of the strings "urgent", "promptly", or "asap", use an array to avoid repeating logic:

any([body.plain.raw, body.html.raw], ilike(., "*urgent*", "*promptly*", "*asap*"))
  "attachments": [
        "file_extension": "xls",
        "file_extension": "docx",
        "file_extension": "xls",


all(input: [T], expression: bool) -> bool
all returns true if all of the values in the original array match an expression, which is provided by the second argument. If the array is empty, then all is vacuously `true.

Example usage

all(attachments, .file_extension == "xls") -> false
all(attachments, .file_extension in ("xls", "docx")) -> true

Tip: Use all if you want to perform an and across the array, and use any to perform an or.


any(input: [T], expression: bool) -> bool
any is the counterpart of all. Instead of checking for the expression to evaluate true for all items, any will check if at least one value returns true.

any(attachments, .file_extension == "xls") -> true
any(attachments, .file_extension in ("xls", "docx")) -> true

Tip: Use all if you want to perform an and across the array, and use any to perform an or.


filter(input: [T], expression: bool) -> [T]
filter is used to generate a new filtered array, only returning items that cause the loop expression to evaluate true.

filter(attachments, .file_extension != "xls") -> [{"file_extension": "docx"}]


length(input: [T]) -> integer
Use length to retrieve the length of an array. If the input array is null, the function returns null. If the input array is non-null but empty, the function returns 0.

length(attachments) -> 3
length(filter(attachments, .file_extension == "xls")) -> 2


any(input: [T], expression: U) -> [U]
map is used to generate a new array, by evaluating an expression for each item in the array.

map(attachments, length(.file_extension)) -> [3, 4, 3]

map is particularly useful when querying an MDM:

map(attachments, .file_name) -> ["unpaid invoice.xlsx", "unpaid invoice.rar"]