Core Filters
This annex is the core filter set. It is closed within a spec version. Implementations MUST provide every filter here, and MUST NOT add a filter with a bare name. A bracketed argument is optional. T? denotes a value that can be null.
Filter behavior is defined by this annex together with the conformance suite fixtures. Where a description defers to fixtures, the fixtures are normative.
Selection and projection
Section titled “Selection and projection”The filter rules govern these four filters, including the current binding and the closed operator set of where.
| Filter | Signature |
|---|---|
where(path, op, value) |
list[object] → list[object] — keeps elements whose path satisfies the comparison |
keep(expr) |
list[T] → list[T] — keeps elements for which expr is true, with current bound to the element |
each(expr) |
list[T] → list[U] — evaluates expr once per element, with current bound to the element |
has(path) |
object → bool — the path exists and is non-null |
Collections
Section titled “Collections”| Filter | Signature and behavior |
|---|---|
length |
list | map | string → int — elements, entries, or code points |
default(v) |
T? → T — yields v when the value is null |
first / last |
list[T] → T? — null for an empty list |
slice(a, b) |
list → list — half-open, and a negative index counts from the end |
reverse |
list → list |
keys / values |
map[string, T] → list — in the map’s key order |
items |
map[string, T] → list[object] — each element is { key: string, value: T } |
flatten |
list[list[T]] → list[T] — one level |
batch(n) |
list[T] → list[list[T]] — chunks of at most n; n MUST be a positive integer |
group_by(path) |
list[object] → map[string, list[object]] — the value at path MUST be a string |
sort |
list → list — by the standard ordering |
sort_by(path) |
list[object] → list[object] — by the standard ordering, stable |
unique |
list → list — keeps the first occurrence of each value, in order |
sum / min / max |
list[number] → number — sum of an empty list is 0; min and max of an empty list yield null |
join(sep) |
list[string] → string |
Strings
Section titled “Strings”| Filter | Signature and behavior |
|---|---|
split(sep, [limit]) |
string → list[string] — limit is the maximum number of elements, and the last holds the unsplit remainder; it MUST be a positive integer |
lower / upper |
string → string — Unicode simple case mapping, independent of locale |
trim |
string → string — removes leading and trailing Unicode whitespace |
capitalize |
string → string — uppercases the first cased character, and lowercases every other cased character |
title |
string → string — uppercases the first cased character of each word, and lowercases every other cased character; a word is a maximal run of characters that are not Unicode whitespace |
starts_with(s) / ends_with(s) |
string → bool |
matches(re) |
string → bool |
extract(re) |
string → string? — the first capture group, or the whole match when the pattern has no group; null when unmatched |
extract_all(re) |
string → list[string] — every non-overlapping match, taking the first capture group of each, or the whole match when the pattern has no group; an empty list when unmatched |
replace(a, b) |
string → string — every occurrence; a is a literal, not a pattern |
format(fmt) |
list → string — each {} in fmt consumes the next element; {{ and }} are literal braces; an arity mismatch is an evaluation fault |
truncate(n) |
string → string — at most n code points; no marker is appended |
indent(n) |
string → string — prefixes every line with n spaces |
wordwrap(n) |
string → string — breaks lines at whitespace so that no line exceeds n code points; a word longer than n occupies its own line and is not broken |
pluralize(one, many) |
int → string — yields one when the value is 1, and many otherwise |
human_size |
int → string — a byte count as text; see below |
strip_html |
string → string — see below |
matches, extract, and extract_all MUST use RE2 syntax: no backreferences, no lookaround, and linear-time matching. Security considerations states why.
human_size scales by 1000, with the units B, kB, MB, GB, TB, and PB. It selects the largest unit for which the magnitude is at least 1. It formats with one decimal place, except for B, which has none. One space separates the number from the unit. 1500 yields 1.5 kB, and 900 yields 900 B.
strip_html removes every tag, and removes the content of script and style elements. It decodes named and numeric character references. It then replaces each run of whitespace with one space, and trims.
Numbers and conversion
Section titled “Numbers and conversion”| Filter | Signature and behavior |
|---|---|
abs |
number → number |
round(n) |
number → number — to n decimal places, half away from zero |
int |
T → int — from a decimal string with an optional sign, or from a float whose fractional part is zero; every other input is an evaluation fault |
float |
T → float — from an int, or from a decimal string |
string |
T → string — from a scalar: null yields the empty string, a bool yields true or false, a timestamp yields RFC 3339 in UTC, and a duration yields its canonical literal; a list or a map is an evaluation fault |
bool |
T → bool — from a bool, or from the strings true and false; every other input is an evaluation fault |
timestamp |
string → timestamp — from RFC 3339 with a mandatory offset; the display zone is the literal’s offset; every other input is an evaluation fault |
duration |
string → duration — from the duration literal grammar; every other input is an evaluation fault |
json |
any → string — canonical serialization: sorted keys, no insignificant whitespace, UTF-8 |
from_json |
string → json — parses one JSON document (RFC 8259); the result is typed json; an input that is not valid JSON, or an object with a repeated member name, is an evaluation fault |
timestamp and duration are the inverses of string on those types, and from_json is the inverse of json, so a value survives a round trip through text. The grammar states the separate rule that a position which declares the type timestamp accepts an RFC 3339 string without a filter.
| Filter | Signature and behavior |
|---|---|
plus(duration) / minus(duration) |
timestamp → timestamp — preserves the display zone |
diff(timestamp) |
timestamp → duration — the receiver minus the argument; the result can be negative |
format_time(fmt) |
timestamp → string — in the display zone; see the directive set below |
to_timezone(tz) |
timestamp → timestamp — changes the display zone only; tz is an IANA identifier or a fixed offset |
format_time directives. The set is closed. A character outside a directive is copied literally. An unrecognized directive is a validation error where fmt is a literal, and an evaluation fault otherwise.
| Directive | Yields |
|---|---|
%Y |
Year, at least four digits |
%m / %d |
Month 01–12 / day of month 01–31 |
%j |
Day of year, 001–366 |
%H / %M / %S |
Hour 00–23 / minute 00–59 / second 00–59 |
%I / %p |
Hour 01–12 / AM or PM |
%L |
Millisecond, 000–999 |
%z / %:z |
Offset from UTC, as +HHMM / as +HH:MM |
%b / %B |
Month name, abbreviated / full |
%a / %A |
Weekday name, abbreviated / full |
%% |
A literal % |
Month and weekday names are English: January to December, Monday to Sunday, and their first three characters as the abbreviation. A name in another language belongs in an extension package, where fixtures verify it.
Escaping and hashing
Section titled “Escaping and hashing”These filters exist so that an author does not write escaping by hand.
| Filter | Signature |
|---|---|
html_escape |
string → string |
json_escape |
string → string |
url_encode |
string → string |
shell_quote |
string → string |
md5 / sha256 |
string → string — lowercase hexadecimal |
base64 |
string → string — the standard alphabet, with padding |
Excluded by contract
Section titled “Excluded by contract”now, random, uuid, and every other source of an ambient or nondeterministic value are not filters, and MUST NOT be added as extensions, because they violate purity. The current instant is run.started_at, a recorded binding rather than a value synthesized inside an expression.
There is no count, no drop, no where_not, and no two-argument equality form of where. length, keep(not …), and where('status', '!=', 'open') are the one representation of each.
Template-autoescaping machinery such as safe and force_escape has no analogue here. In expressions there is no output document to autoescape, and in local templates escaping is contextual and mandatory.
There is no hmac and no other signing filter, and no canonical package will hold one. A signature requires a key, a key is a credential, and no expression can read a credential. An action that requires a signed request receives the signature from its connector’s executor.
slugify is left out of core, because implementations diverge silently on its Unicode behavior. The canonical text package defines it, with fixtures that pin the behavior.
relative_to is left out of core for the same reason: a human-readable interval is wording, and wording is a locale. The canonical human package defines it, with the locale as an explicit argument.
Format-directed time parsing is left out of core and out of the current library. The timestamp filter converts RFC 3339, which is unambiguous. A parse directed by a pattern such as %d/%m/%Y needs a locale, a century rule for a two-digit year, and a policy for an impossible date. Each is a place where implementations differ without saying so.