Meridian handbook
Templates

Filters

Every filter a Meridian template can use, grouped (text, locale-aware formatting of numbers, dates and money, rich text, URLs and encoding, lists, and the Pongo2 built-ins), with its argument, default, output and example.

A filter transforms the value to its left: {{ value|filter }}, or with its one argument {{ value|filter:arg }}. Filters chain left to right. This page lists every filter a site’s templates can use, in both bundles; engine-dmo template api prints the same list from the engine itself.

{{ entry.title|upper }}
{{ entry.summary|truncate:120 }}
{{ entry.publishedAt|date:"long" }}
{{ entry.description|plain|excerpt:160 }}
{{ listing.website|url }}

Two things hold for every filter:

  • The output is escaped unless the filter says it returns markup. A filter that returns markup returns one of the engine’s safe values, which prints as it is.
  • Locale-aware filters read the request locale. You never pass a locale: the same template prints 14 September 2026 on an English page and 2026年9月14日 on a Japanese one, with the locale’s numerals, calendar and hour cycle, and times in the site’s time zone unless the value carries its own.

A filter that does not recognise its input (a date filter given text that is not a date, say) returns the input unchanged.

Dates and times

FilterArgumentOutput
dateA style or skeleton; medium by defaultThe date in the locale’s words and order
datetime"<date style> <time style>"; "medium short" by default, or "relative"The date and time joined as the locale joins them
timeshort (default) or medium (with seconds)The time of day in the locale’s hour cycle
isononeRFC 3339 with the value’s own offset, the same in every locale, for a datetime attribute
relativenoneRelative to now: in 3 days, yesterday, 2 hours ago
daterangeAn end date, or a style (short, medium, long, full)Two dates with their shared parts collapsed: 2–3 Nov 2026, 14 Aug – 2 Nov 2026
durationlong (default), short or narrowA length of time in words: 2 hours, 30 minutes

date styles are short, medium, long and full; the skeletons yMd, yMMMd, yMMMMd, yMMMEd, yMMMMEEEEd, Md, MMMd, MMMMd, MMMEd, yMMM, yMMMM, y and Ed; relative; and zone, which prints the IANA name of the zone the value is shown in. Each renders in the locale’s own order. An unknown style renders as medium.

{{ a.publishedAt|date }}  {# 14 Aug 2026 #}
{{ a.publishedAt|date:"short" }}  {# 14/08/2026 #}
{{ a.publishedAt|date:"full" }}  {# Friday, 14 August 2026 #}
{{ a.publishedAt|date:"yMMMM" }}  {# August 2026 #}
{{ a.publishedAt|date:"MMMEd" }}  {# Fri 14 Aug #}
{{ a.publishedAt|datetime:"long short" }}  {# 14 August 2026 at 10:00 #}
{{ a.publishedAt|time }}  {# 10:00 #}
<time datetime="{{ a.publishedAt|iso }}">{{ a.publishedAt|date:"long" }}</time>
{{ event.startsAt|relative }}  {# in 3 days #}
{{ today|daterange:tomorrow }}  {# 2–3 Nov 2026 #}
{{ "PT2H30M"|duration }}  {# 2 hours, 30 minutes #}
{{ 5400|duration:"short" }}  {# 1 hr, 30 mins #}

The date filters accept a date or datetime field’s value, now, today, tomorrow, yesterday, the engine’s dates (entry.publishedAt), and text in RFC 3339 or YYYY-MM-DD form. A wall-clock time that carries its own zone (an event in Paris on a London site) keeps it; any other instant is shown in the site’s time zone.

daterange takes either a start with the end as the argument (start|daterange:end), or one value holding both: a list of two dates, or a map with start and end, from and to, startsAt and endsAt, or local and localEnd.

duration reads an ISO 8601 duration (PT2H30M, days allowed), a Go duration (2h30m) or a number of seconds.

A Go reference layout (|date:"2 Jan 2006") still renders, the same way in every locale, and engine-dmo check warns about it (date-layout) with the named format to use instead.

Numbers and money

FilterArgumentOutput
numberFixed decimal places; without it, up to three as neededThe locale’s grouping, decimal separator and digits: 1,234.5, 1 234,5, ١٬٢٣٤٫٥
currencyAn ISO 4217 codeAn amount in that currency for the locale, with the currency’s own minor units: £12.50, JP¥1,500
moneynoneA money value, which carries its own currency, formatted as currency
percentageMost decimal places; 0 by defaultA fraction as a percentage: 0.125 is 13%
ordinalnoneAn ordinal by the locale’s rules: 3rd, 3., 第3
unit"<unit> <width>", width short (default), long or narrowA quantity of a unit: 12.3 kilometres
distancenoneMetres in the viewer’s measurement system: 7.5 mi in en-GB and en-US, 12,1 km in de
filesizenoneBytes as a size in decimal units: 1.6 MB
listand (default), or, unit, unit-narrow, and-shortA list joined as the locale writes it: A, B and C, A, B or C
{{ 1234.567|number }}  {# 1,234.567 in en-GB, 1.234,567 in de #}
{{ walk.lengthKm|number:1 }} km  {# 12.3 km #}
{{ 12.5|currency:"GBP" }}  {# £12.50 #}
{{ listing.fee|money }}
{{ 0.1234|percentage:2 }}  {# 12.34% #}
{{ 22|ordinal }}  {# 22nd #}
{{ 20|unit:"celsius long" }}  {# 20 degrees Celsius #}
{{ walk.metres|distance }}  {# 4.7 mi #}
{{ asset.bytes|filesize }}  {# 1.6 MB #}
{{ ["Banbury", "Bicester"]|list:"or" }}  {# Banbury or Bicester #}

currency accepts a number, a decimal written as text, or a money value; with no code and no currency in the value it returns the input unchanged. The units unit knows are year, month, week, day, hour, minute, second, millisecond, kilometer, meter, centimeter, millimeter, mile, yard, foot, inch, hectare, acre, kilogram, gram, pound, ounce, liter, milliliter, celsius, fahrenheit, kilometer-per-hour, mile-per-hour, byte, kilobyte, megabyte, gigabyte, terabyte and percent.

Text

FilterArgumentOutput
upper, lowernoneUpper or lower case by the locale’s rules (Turkish i upper-cases to İ)
titlenoneTitle case by the locale’s rules
capfirst, ucfirstnoneThe first character upper-cased by the locale’s rules
ucwordsnoneThe first letter of each word upper-cased
camel, pascal, kebab, snakenoneopeningHours, OpeningHours, opening-hours, opening_hours. Words split on anything that is not a letter or digit and on a change from lower to upper case
truncateLength in grapheme clusters; 80 by defaultCut to that length with an ellipsis, only when longer
wordsNumber of wordsThe first n words, with an ellipsis when cut
excerptLength in grapheme clusters; 160 by defaultPlain text, markup and Markdown removed, whitespace collapsed, cut at a word with an ellipsis
trimnoneLeading and trailing whitespace removed
lengthnoneA string’s length in grapheme clusters (what a reader counts as characters), or a list’s or map’s length
wordcountnoneThe number of words
striptagsnoneHTML tags removed, entities decoded (the result is escaped again when printed)
replace"from=>to", or a map of replacementsEvery occurrence replaced. Any other argument is an error
maskCharacters to keep; 4 by defaultAll but the last n characters as bullets: ••••••7890
asciinoneAccents folded, anything with no ASCII form dropped: Café Ætna becomes Cafe tna
transliterateA language codeEach word romanised, with the language’s rules: Straße becomes Strasse with de
slugifymixed (default), transliterate or nativeA URL slug: cafe-strasse; native keeps scripts such as 東京-tower
contains, startsWith, endsWithThe text to look fortrue or false
nl2brnoneNewlines as <br>, the text escaped. Returns markup
widontnoneThe last two words joined with a no-break space, the text escaped. Returns markup
markdownnoneA safe subset of Markdown as HTML. Returns markup
stripMarkdownnoneMarkdown as plain text
{{ entry.title|truncate:40 }}
{{ entry.summary|excerpt:155 }}
<h2>{{ entry.title|widont }}</h2>
{{ entry.notes|nl2br }}
{{ entry.title|replace:"&=>and" }}
{{ entry.reference|mask:4 }}

markdown renders ATX headings, paragraphs and hard breaks, emphasis and strong, inline code and fenced code blocks, block quotes, bullet and numbered lists one level deep, and links whose scheme url accepts. Every character of the input is escaped first, raw HTML stays text, and a link to javascript: keeps its text and loses its link.

There is no translation filter: interface text comes from t(), which knows the request locale (see Functions).

Rich text

FilterArgumentOutput
ptextA serialiser set; default by defaultA portable_text value as HTML. Headings get id anchors from their text (getting-here, then getting-here-2). Object blocks render through ptext/<set>/<block key>.html. Returns markup
plainnonePortable Text or Markdown as plain text
tocnoneA <nav> of nested lists linking to the rich text’s headings, the same ids ptext gives them; empty when there are none. Returns markup
<nav>{{ entry.body|toc }}</nav>
{{ entry.body|ptext }}
{{ entry.body|ptext:"compact" }}
<meta name="description" content="{{ entry.body|plain|excerpt:155 }}">

ptext renders every span escaped. An object block with no serialiser template renders nothing on a public page. A serialiser template receives the block as node, its position as index, and the page’s globals; naming a set that has no templates at all is an error. See Rich text serialisers.

URLs, JSON and attributes

FilterArgumentOutput
urlnoneThe URL when its scheme is safe (relative, #, ?, http, https, mailto, tel, sms, otpauth), otherwise #. A protocol-relative //host becomes #. Escaped for an attribute. Required on a variable in href, src and the other URL attributes
jsonnoneThe value as JSON with <, >, & and the JavaScript line separators escaped, safe inside a <script> element. Required on a variable inside <script>
attrnoneA map as escaped HTML attributes: true prints the bare name, false and nothing leave it out, a list is joined with spaces
urlencodenonePercent-encoded for a query string (spaces as +)
iriencodenoneAn IRI encoded for use in a URL
escapejsnoneEscaped for a JavaScript string. Prefer json
icsUrlnoneAn event entry’s .ics address when its route has a calendar, otherwise empty
<a href="{{ listing.website|url }}">{{ t('listing.website') }}</a>
<script>window.page = {{ entry.title|json }};</script>
<a href="{{ entry|icsUrl }}">{{ t('event.add_to_calendar') }}</a>

json fails the render on a value that holds helper functions, such as a geo value or a document from a list; print its plain parts instead ({{ entry.location.lat|json }}). The page’s own entry, tags, addresses, strings and numbers encode.

A template has no map literal, so attr is for a map that comes from the context, such as part of settings.

Escaping

FilterOutput
escapeThe value escaped for HTML. Escaping is automatic; this is for an explicit call
rawPrints an engine-produced safe value as it is. On anything else it is an error: raw requires a Safe value

safe, safeseq, e, escapeseq and json_script are not available. See Autoescaping.

Lists and values

FilterArgumentOutput
defaultA fallbackThe argument when the value is empty, zero or false
default_if_noneA fallbackThe argument only when the value is nothing
first, lastnoneThe first or last item of a list, or character of a string
joinA separatorA list joined with it
splitA separatorA string split into a list
slice"start:end", either side optional, negative from the endA slice of a list or string
make_listnoneA string as a list of characters
length_isA numberWhether the length equals it
randomnoneA random item of a list
addA number, string or listAdds to a number, appends to a string or list
divisiblebyA numberWhether the value divides by it
yesno"yes,no,maybe"One of the words for true, false and nothing
pluralize"suffix" or "singular,plural"; s by defaultThe plural suffix by count. Prefer t(id, count), which follows the locale’s plural rules
integer, floatnoneThe value as an integer or a float
floatformatDecimal places; negative to drop trailing zerosA float rounded. Prefer number for display
stringformatA Go format verb, such as "%05d"The value formatted with it
get_digitA position from the rightThat digit

Pongo2 text built-ins

These come from Pongo2 and are available unchanged. They do not know the locale.

FilterArgumentOutput
addslashesnoneQuotes escaped with backslashes
center, ljust, rjustA widthPadded with spaces to the width
cutTextEvery occurrence removed
truncatecharsA lengthCut to that many characters, with an ellipsis
truncatewordsA numberCut to that many words, with ...
wordwrapA width in wordsWrapped onto new lines
linenumbersnoneEach line numbered
phone2numericnoneLetters on a phone keypad as digits
linebreaks, linebreaksbr, urlize, urlizetrunc, removetagsThese produce HTML, which is then escaped and prints as visible tags. Use nl2br and markdown instead
truncatechars_html, truncatewords_htmlA lengthCut HTML and close its tags. They print their input as markup without escaping it, so never apply them to content; use truncate, words or excerpt

Development only

dump prints a value as a navigable tree. It is not registered when engine.environment is production, so a stray |dump fails to compile there rather than leaking data.

Not available

timesince, timeuntil, filesizeformat, dictsort, dictsortreversed and unordered_list are removed: use relative, filesize, orderBy() in a query, and a loop or {% nav %}. A filter that does not exist is a compile error naming it: Filter 'frobnicate' does not exist.