Meridian handbook
Templates

Globals

The variables every public template can read (site, sites, locale, locales, page, request, now, today, settings, singletons, nav, csrf, assets, devMode) with every key each one holds.

Globals are the variables every public page template receives, whatever the page is: entry pages, index pages, the search page, error pages, block and rich text templates, components and partials. The callable globals (path(), query(), t() and the rest) are on Functions; what each kind of page adds is on The page context.

<html lang="{{ locale.code }}" dir="{{ locale.direction }}">
<title>{{ page.title }} | {{ site.name }}</title>
<p>{{ settings.contact.phone }}</p>
<footer>&copy; {{ now|date:"y" }} {{ site.organisation }}</footer>

site

The site the request is for.

KeyValue
site.keyThe site’s key, its folder name under config/sites/
site.nameThe site’s name in the request locale
site.shortNameshort_name in the request locale, empty when not set
site.organisationorganisation from site.yaml
site.baseUrlThe site’s absolute base URL, on its configured domain
site.domainsThe site’s domains list
site.countrycountry, an ISO 3166 code
site.timezonetimezone, an IANA zone name
site.defaultCurrencydefault_currency, an ISO 4217 code. site.currency is the same value
site.embedOnlyWhether the site runs in embed-only mode
site.robotsnoindex, follow in embed-only mode (the engine also sends it as an X-Robots-Tag header), else empty

sites

On a project with more than one site, every site, each key, name (in the request locale) and baseUrl. Not set on a single-site project. Use siteUrl() to link to a page on another site.

{% if sites %}<nav aria-label="{{ t('nav.sites') }}">{% for s in sites %}<a href="{{ s.baseUrl|url }}"{% if s.key == site.key %} aria-current="true"{% endif %}>{{ s.name }}</a>{% endfor %}</nav>{% endif %}

locale and locales

locale is the request locale:

KeyValue
locale.codeThe locale code, such as en-GB
locale.labelIts name in its own language, such as English or 日本語
locale.isDefaultWhether it is the site’s default locale
locale.directionltr or rtl
locale.languageThe language subtag, such as en
locale.currencyThe locale’s currency code
locale.firstDayOfWeek0 for Sunday to 6 for Saturday
locale.calendarThe calendar the formatting filters use, such as gregory
locale.numberingThe numbering system, such as latn

locales is the site’s enabled locales, each with the same keys. For a language switcher that lists only the locales the current page is published in, use localeSwitcher() or page.alternates; locales lists them all.

page

The page being rendered: always uri, canonical, title, type and isPreview, and more on some pages (alternates, index, noindex, description, landing). The keys of each kind of page are on The page context.

request

What a template may know about the request. It is deliberately small: no headers, no cookies, no IP address, so a page stays cacheable and private.

KeyValue
request.pathThe request path, with the locale prefix
request.queryThe query string, as a map from name to value: request.query.q
request.isSecureWhether the request came over HTTPS (directly, or with X-Forwarded-Proto: https)

Reading a query parameter outside the canonical set makes the page uncacheable, and engine-dmo check warns at the read (uncacheable-query). The canonical set is page, sort, q, type, tag, lat, lon, km, and every field a route of the site lists in facets:.

now, today, tomorrow, yesterday

VariableValue
nowThe time of the request, in the site’s time zone
todayMidnight at the start of the request’s date, in the site’s time zone
tomorrow, yesterdayMidnight of the day after and the day before

They are never in the server’s zone or in UTC, so a site in summer time does not see today begin at one in the morning. Format them with the date filters: {{ today|date:"full" }}. Compare them with dates directly: {% if entry.endsAt < now %}.

settings

The site’s settings: block from site.yaml, nested maps and lists as written. It is the place for values a template needs that are not content, such as a contact email or a social profile URL.

config/sites/main/site.yaml
settings:
  contact:
    email: hello@example.test
  social:
    instagram: https://www.instagram.com/example
<a href="mailto:{{ settings.contact.email }}">{{ settings.contact.email }}</a>
<a href="{{ settings.social.instagram|url }}">Instagram</a>

Secrets never belong here: configuration files may not hold secrets, which come from environment variables.

singletons

The site’s singleton documents by type key. singletons.<type> is that type’s one document on the site, published in the request locale, shaped as entry is, or empty when nothing is published. It is read only when a template reaches it, so listing singletons costs nothing on a page that does not use them.

{% with footer = singletons.siteFooter %}
  {% if footer %}{{ footer.text|ptext }}{% endif %}
{% endwith %}

engine-dmo check reports a key that is not a singleton type on the site (unknown-singleton). Singletons are declared in the schema; see Content types.

The site’s navigation by slot. Each slot is declared under nav: in site.yaml and filled in the admin:

config/sites/main/site.yaml
nav:
  main: { id: nav_q69bmrm00d, label: { en-GB: Header }, depth: 1 }
  footer: { id: nav_fw01bn2kgg, label: { en-GB: Footer columns }, depth: 2 }

nav.main is a list of items, resolved for the site and the request locale, with targets that are not available left out. Each item is:

KeyValue
labelThe label in the request locale
urlThe address: a document’s live path, an asset’s URL, a site path, an external URL or a type’s index page; empty for a heading
kindWhat the item points at
isExternalWhether it leaves the site
isCurrentWhether it links to the page being rendered
isActiveWhether it or one of its descendants is current
depthIts level, from 1
childrenIts child items

Render a tree with the nav tag (see Tags):

<nav aria-label="{{ t('nav.main') }}"><ul>
{% nav item in nav.main %}
  <li><a href="{{ item.url|url }}"{% if item.isCurrent %} aria-current="page"{% endif %}>{{ item.label }}</a>
  {% ifchildren %}<ul>{% children %}</ul>{% endifchildren %}</li>
{% endnav %}
</ul></nav>

csrf

The token a form posting back to the engine must carry. It is minted when a template prints it, which sets the binding cookie, so a page that never prints it stays free of cookies. csrfToken() returns the same token and csrfInput() the hidden input that carries it.

<form method="post" action="{{ path('/enquiry') }}">
  {{ csrfInput() }}
  ...
</form>

assets

Files and bundles from public/:

CallPrints
assets.url('css/site.css')The file’s path with a version from its modification time (/css/site.css?v=tnuoz7), so a changed file is fetched again
assets.css('main')A <link rel="stylesheet"> for the bundle main
assets.js('main')A <script type="module"> for the bundle main
assets.js('legacy', false)A classic <script defer>

Files resolve along the site’s public chain: public/<site key>/, then public/_shared/, then public/. Without a build integration, bundle main is css/main.css and js/main.js. With the Vite extension, a bundle resolves to the built, content-hashed files, and to the Vite dev server under serve --dev. See The frontend build.

devMode

true when the engine runs with engine-dmo serve --dev, otherwise false. Use it for development-only markup; never to hide content.