Meridian handbook
Templates

Functions

Every function a Meridian template can call (links and URLs, interface strings and locale helpers, content queries, head and structured data, feeds, forms and security, analytics, assets and images, embeds, sigmx, upstream sources, and the DMO functions) with arguments, keywords, return values and examples.

Functions are globals called with parentheses: {{ path('/places') }}. Some take keyword arguments (name=value, see Keyword arguments); engine-dmo check refuses a keyword a function does not take, and any keyword to a function that takes none. A function that returns markup returns a safe value that prints as it is.

This page is the complete list. Field values have functions of their own (an image’s picture(), a link’s link(), opening hours’ isOpenAt()), listed per field type by engine-dmo describe fieldtypes.

Summary

FunctionReturns
path(), url(), siteUrl()A path or URL on this or another site
localeUrl(), localeSwitcher()The current page in another locale; a language switcher
t(), plural(), dir(), bidi()Interface strings and locale helpers
query(), nearby(), children(), ancestors(), smartList(), search()Content
head(), jsonld()The head metadata and structured data
feedUrl(), feedLinks(), calendarUrl()Feeds and calendars
searchForm()The search form
csrfToken(), csrfInput()The form token
enquiryForm(), subscribeForm()A declared endpoint’s form
track(), reveal(), impressions(), analytics()Measured links and the analytics script
assets.*, preload(), images.file()Static files, bundles and images
embed()A privacy-preserving embed
sigmx.fragment()Attributes for in-place updates
source()An upstream data source
aiDisclosure()The AI-assistance disclosure
dump()A debugging view (development only)
promoted(), promotionLabel(), memberTier(), hasAffiliateLinks()DMO bundle only

path

path('/literal')            a path on this site, with the request locale's prefix
path('type')                the type's index page in the request locale
path('type', entry)         the entry's path (any document value)
path()                      the home page in the request locale
path(..., locale='code')    the same in another locale
path(locale='code')         the current page in that locale
<a href="{{ path('/') }}">{{ site.name }}</a>
<a href="{{ path('listing') }}">{{ t('nav.places') }}</a>
<a href="{{ path('/about', locale='ja-JP') }}">日本語</a>

path('listing') gives the index’s segment in each locale (/places, /ja/spots), so a template links to an index without knowing it. A literal path is prefixed and not translated: path('/places', locale='ja-JP') is /ja/places. locale= returns empty for a locale the site does not enable. With no locale=, an argument that is neither a path nor a type with an index is returned unchanged. Keywords: locale.

url

url(...) takes what path() takes and returns an absolute URL on the origin the visitor is on, port included, so a page served on localhost:8080 links to localhost:8080. The canonical URL and structured data use the site’s configured domain instead. Keywords: locale.

<link rel="alternate" hreflang="ja-jp" href="{{ url(locale='ja-JP') }}">

siteUrl

siteUrl(path or type, site=, locale=) is an absolute URL on a site of the project, on that site’s configured domain. The argument is a path starting with / (prefixed for the locale) or a type key (the type’s index page there). site= is the other site’s key, this site when not given; locale= the locale to link to where that site enables it, its default locale otherwise. When the request came in on a port and the other site lists its domain without one, the link keeps the port.

<a href="{{ siteUrl('/events', site='towns') }}">{{ t('nav.events') }}</a>
<a href="{{ siteUrl('event', site='towns', locale=locale.code) }}">{{ t('nav.events') }}</a>

localeUrl

localeUrl('cy-GB') is the current page’s path in another locale, or empty where the page has no translation published on this site. A page with no alternates of its own (the search page, an endpoint’s response) exists in every locale at the same path.

localeSwitcher

localeSwitcher() prints a <nav> listing the locales the current page exists in, the current one marked with aria-current="page", each link with hreflang and lang. It is labelled from the string nav.language when the catalogue has it, and prints nothing when the page exists in one locale only.

<nav class="mrd-locales" aria-label="Language"><ul><li><span aria-current="page" lang="en-GB">English</span></li><li><a href="/ja/access" hreflang="ja-jp" lang="ja-JP">日本語</a></li></ul></nav>

Interface strings and locale

t

Every piece of interface text a template prints comes from the string catalogue, config/strings/<locale>.yaml, so a template carries no literal English and a new locale is a file:

config/strings/en-GB.yaml
strings:
  nav.places: Places
  index.count:
    one: "{count} place"
    other: "{count} places"
  pager.page_of: "Page {page} of {pages}"
{{ t('nav.places') }}
{{ t('index.count', pagination.total) }}
{{ t('pager.page_of', 'page', pagination.page, 'pages', pagination.pages) }}
CallResult
t(id)The string in the request locale
t(id, count)When the second argument is a number: the plural form for it by the locale’s CLDR rules (zero, one, two, few, many, other), with {count} filled
t(id, name, value, ...){name} filled with each value. After a count, the pairs follow it

The lookup walks the request locale, its fallback chain and the source locale. An id defined nowhere prints as the id itself, and engine-dmo check warns at the line (unknown-string). t() takes no keyword arguments.

plural

plural(n) is the CLDR plural category of n in the request locale (one, few, other and so on), for the rare branch t(id, n) cannot express.

dir

dir() is the request locale’s direction, ltr or rtl, the same as locale.direction.

bidi

bidi(text) wraps text in <bdi>, so a name in another script does not disturb the order of the text around it. Returns markup.

Content

These are described in full on Querying content:

FunctionReturns
query('type')A query over one type: .where(), .tag(), .near(), .orderBy(), .limit(), .offset(), .exclude(), .organisation(), .onePer(), .with(), then .all(), .first(), .count(), .exists() or .ids()
nearby(entry, km, type)Up to 12 documents near the entry, nearest first
children(doc), ancestors(doc)A nesting document’s children (title order) or ancestors (root first)
smartList('key')A public smart list: .limit(n), .all(), .first(), .count()
search(q)The search service: .types(), .tags(), .near(), .sort(), .facets(), then .paginate(n) or .limit(n)

Head and structured data

head() prints the page’s <title>, meta description, canonical link, hreflang alternates with x-default, the robots directive, Open Graph and Twitter card tags, and rel=prev and rel=next on a paged page, all from what the page context already holds. Print it once, in <head>. See SEO and structured data.

jsonld

jsonld() prints the page’s structured data as one JSON-LD @graph, cross-referenced by @id, built from the types’ structured_data mappings and the site’s settings; jsonld(entry) prints one document’s entity alone. See SEO and structured data.

<head>
  <meta charset="utf-8">
  {{ head() }}
  {{ jsonld() }}
</head>

Feeds and calendars

feedUrl

feedUrl(type, format=) is the path of a type’s feed in the request locale, empty when its route has none. format= is rss (the default), atom or json.

feedLinks() prints a <link rel="alternate"> for each of the site’s feeds in the request locale; feedLinks(type) for one type’s. Put it in <head>.

calendarUrl

calendarUrl(type) is the path of a type’s calendar feed in the request locale, empty when its route has none. For one event’s .ics, use the icsUrl filter: {{ entry|icsUrl }}.

Feeds and calendars are declared on a route; see the routes reference.

Forms and security

searchForm

searchForm() prints a GET form to the site’s search route that works without JavaScript, with a labelled search input named q. It prints nothing when the site routes no search.

<form class="mrd-search" role="search" method="get" action="/search"><label for="mrd-q">What are you looking for?</label> <input id="mrd-q" type="search" name="q" value="" autocomplete="off"> <button type="submit">Search</button></form>

csrfToken and csrfInput

csrfToken() is the token a form posting back to the engine must carry, the same value the csrf global prints. csrfInput() prints the hidden input that carries it. For an anonymous visitor both print nothing and set no cookie, since an anonymous post is checked by its origin; for a signed-in visitor they print a token bound to the session, and such a page is not stored in the page cache. A {% cache %} block that names either is an error. Under engine-dmo render both are always empty. See The CSRF token.

enquiryForm and subscribeForm

enquiryForm('endpoint') describes a declared endpoint’s form: its action, its inputs with labels, rules and errors, and its hidden fields. Printed as it is, {{ enquiryForm('contact') }}, it draws the complete accessible form. It has no render() method (only a form field’s value has one), and an unknown key prints nothing; engine-dmo check reports neither. subscribeForm('endpoint') does the same for a subscription endpoint. Both come from the forms module, in both bundles. See Forms.

track

track(entry, kind, url) returns a tracked outbound URL (/r/v1/...) that records the click against the document, then redirects to the destination. kind is one of website, booking, phone, email, directions, social, event, download or share (anything else counts as website). url is a string, a link value or a phone value. An empty destination returns empty. On a staff preview it returns the destination itself. In an href, put it through |url as any variable there must be.

<a href="{{ track(entry, 'website', entry.website)|url }}" rel="noopener">{{ t('listing.website') }}</a>

reveal

reveal(entry, kind, value) is track() for a contact detail: with phone the value becomes a tel: link, with email a mailto: link, and the returned tracked URL records the interaction before handing over to it. It works without JavaScript.

<a href="{{ reveal(entry, 'phone', entry.phone)|url }}">{{ entry.phone.display }}</a>

impressions

impressions() prints the script that reports which cards marked data-mrd-impression a visitor actually saw (in the viewport for half a second, once per session per page). With no JavaScript nothing is recorded.

analytics

analytics() prints the cookieless analytics client that records impressions and in-page interactions. Print it once, before </body>. See Analytics.

Assets and images

assets and preload

assets.url(path), assets.css(bundle) and assets.js(bundle, module) print files and bundles from public/ (see Globals). preload(bundle, ...) prints the preload hints of the named bundles (main when none is named) from the build manifest, and nothing without a build integration or under the dev server. See The frontend build.

images.file

images.file('img/team.jpg') turns a file under public/ into an image value, with the same img(), picture() and figure() a media asset has, through the same image pipeline. A path with no image under public/ fails the render. See Images.

{{ images.file('img/team.jpg').img('card') }}

embed

embed(url, title=) prints a third party’s player, map or post as a facade: a notice, a button that loads it, and a plain link to it. Nothing loads from the provider until the visitor presses the button. title= names what is embedded, for the button and the announcement; the provider’s name when not given. YouTube embeds use the privacy domain.

{{ embed('https://www.youtube.com/watch?v=dQw4w9WgXcQ', title='A walk along the canal') }}

sigmx.fragment

sigmx.fragment(names, on=, debounce=, history=, focus=) prints the attributes that make a form or link ask the server for the named fragments of the page and swap them in place, the address bar following:

KeywordValue
onThe event that asks: change (the default), input, submit, or click on a link
debounceWait this long after the last event, such as '300ms'
historypush (the default), replace or none
focusnew: move focus to the first item an append added
<form method="get" action="{{ path('listing') }}" {{ sigmx.fragment('results', on='input', debounce='300ms') }}>

Every name must be a fragment some template on the site declares with {% fragment %}; engine-dmo check refuses one that none does. See Forms for fragments and in-place updates.

source

source('name') reads an upstream declared in sources.yaml, cached and behind its circuit breaker. It is empty when the source is not declared or cannot answer, so a template must handle the empty case. See Integrating.

aiDisclosure

aiDisclosure(entry) prints the disclosure sentence when the entry carries AI-assisted text that the site’s policy discloses (entry.aiAssisted), and nothing otherwise. The sentence is the catalogue’s ai.disclosure string in the request locale, or the engine’s English sentence when the catalogue has none.

{% if entry.aiAssisted %}<p class="disclosure">{{ aiDisclosure(entry) }}</p>{% endif %}

dump

Development only. dump(value) prints a value as a navigable tree, and dump() the whole context. It is not registered when engine.environment is production, so a stray call fails to compile there.

DMO functions

engine-dmo

These functions are part of the DMO bundle (engine-dmo). In engine-cms a template that uses one fails the check with the name of the module it belongs to.

promoted('slot') is the documents of a promotion slot’s live promotions, oldest start first, each an entry with promotion (id, slot, label, startsAt, endsAt) and organisationId. For a slot sold per landing page, promoted('slot', landing) names the page; on a landing page promoted('slot') reads it. promotionLabel(item) prints the site’s promotion label as visible text, to put inside each placement’s link. A slot must be declared in the site’s promotions.yaml, and a template that renders promoted() without printing promotionLabel() fails the check (promoted-unlabelled).

{% set items = promoted('home-feature') %}
{% if items %}<ul class="cards">
  {% for item in items %}<li data-mrd-promotion="{{ item.promotion.id }}"><a href="{{ item.uri|url }}">{{ promotionLabel(item) }} {{ item.title }}</a></li>{% endfor %}
</ul>{% endif %}

See Promotions.

memberTier

memberTier(entry) is the tier key of the active membership the entry’s organisation holds on the page’s site, or empty when it holds none or the document has no organisation. See Memberships.

hasAffiliateLinks(value) is whether a value (an entry, its relations included) holds a link through an affiliate programme, so the page can print the disclosure:

{% if hasAffiliateLinks(entry) %}<p class="affiliate-disclosure">{{ t('affiliate.disclosure') }}</p>{% endif %}

See Affiliates.