The page context
Exactly what each kind of page template receives (a document's entry and its common fields, an index page's items, pagination and facets, the search page's results, error pages, block and rich text templates) and how to print the real context of any URL.
A template’s context is the set of variables it can read. Every page template receives the globals (site, locale, page, request, now and the rest) and the functions (path(), query(), t() and the rest). Each kind of page then adds its own variables, described here.
To see the context of a real page with its values, run:
engine-dmo describe route /places/the-red-lion --json context in the output is every variable the template receives, with its value; template is the file the site’s chain resolved. It needs the project’s database. engine-dmo template api --json lists the same surface without a database, with your own types’ fields.
Every template starts from the same globals and functions; the kind of page decides what is added on top:
flowchart TD G["Globals and functions<br/>site, locale, page, request, t(), path(), query() ..."] --> D["A document's page<br/>entry, its type's key"] G --> I["An index page<br/>items, pagination, facets"] I --> L["A landing page (engine-dmo)<br/>plus landing"] G --> S["The search page<br/>results, pagination"] G --> E["Error pages<br/>page only, no entry"] D --> B["Block and rich text templates<br/>block or node, plus entry and page"]
Every page
| Variable | What it is |
|---|---|
page | The page itself: always uri, canonical, title, type and isPreview; the other keys depend on the kind of page, below |
| The globals | site, locale, locales, request, now, today, tomorrow, yesterday, settings, singletons, nav, csrf, assets, devMode, and sites on a project with more than one site. See Globals |
| The functions | path(), url(), t(), query() and every other function. See Functions |
A document’s page
Rendered with <type>/entry.html (or the type’s or variant’s template:). The document is entry, and the same value is also available under the type’s key, so listing/entry.html can write {{ listing.title }} or {{ entry.title }}:
{% extends "base.html" %}
{% block body %}
<article>
<h1>{{ entry.title }}</h1>
<p>{{ entry.summary }}</p>
{% if entry.publishedAt %}<p><time datetime="{{ entry.publishedAt|iso }}">{{ entry.publishedAt|date:"long" }}</time></p>{% endif %}
{{ entry.description|ptext }}
{% for tag in entry.tags %}<span class="tag">{{ tag.label }}</span>{% endfor %}
</article>
{% endblock %} page on a document
| Key | Value |
|---|---|
page.uri | The path of this page, with its locale prefix |
page.canonical | The absolute canonical URL, on the site’s configured domain. For a document shared from another site, its address on the site that owns it |
page.title | The document’s title |
page.type | The type key |
page.isPreview | false on a public page |
page.alternates | The locales the document is published in on this site: each locale, hreflang, path and url |
Fields every document has
| Path | Value |
|---|---|
entry.id | The document’s id |
entry.type | The type key |
entry.uri | The document’s path on this site in the request locale, with the locale prefix. Print it in an href with |url |
entry.url | The same as an absolute URL on the site’s configured domain |
entry.slug | The slug in the request locale |
entry.title | The title in the request locale |
entry.status | published on a public page |
entry.locale | The request locale’s code |
entry.createdAt, entry.updatedAt, entry.publishedAt | Dates in the site’s time zone. publishedAt is the locale’s first publication, updatedAt its latest published revision. A type that declares a field of the same name keeps its own value |
entry.translations | A map from every enabled locale’s code to locale, exists (published with a live path on this site), stale (it trails its source), path and url (empty when it does not exist) |
entry.tags | The document’s public tags, each key, label (in the request locale), kind, parent, depth, colour, description |
entry.seo | title (the site’s seo.title_template applied), description (the summary as plain text), image (the site’s seo.default_image, absolute), noindex and canonical. A type that declares its own seo field keeps it |
entry.overridden | The names of the fields whose value on this page is this site’s own override rather than the shared document’s, sorted; empty otherwise. Every field value is already the resolved one |
entry.aiAssisted | Whether the revisions shown carry AI-assisted text the site’s disclosure policy discloses; aiDisclosure(entry) prints the sentence |
entry.variant | The entry variant’s key, on a type that declares variants only |
entry.parent | On a type whose route nests: the parent document, one level deep; empty at a root |
entry.ancestors | On a nesting type: the ancestors, root first; empty at a root or on a flat type |
entry.children | On a nesting type: the published children in the request locale, title order |
entry.siblings | On a nesting type: the parent’s other children, title order |
entry.<field> | Every field the type declares, as its field type renders it |
engine-dmo check holds entry.<field> and <type key>.<field> to the schema, so {{ entry.photo }} on a type without a photo field is an error. It does not follow a document reached any other way: a loop variable over items, query() results or a relation ({% for w in entry.relatedWalks %}{{ w.photo }}), or a component prop. A field those do not have prints nothing, with no error or warning at check or at render, even with template.strict: true. Check those names against engine-dmo describe type <type> and read the output of engine-dmo render.
There is no entry.author. A byline is a field you declare, such as a relation to a person or a text field.
With engine-dmo, the modules add properties: entry.organisation (the organisation that owns a listing, from the CRM module, when it has one) and entry.landings (the live landing pages a place has, or that a document is a result of). See the DMO section.
Field values
Each field prints sensibly on its own and has properties and helpers for everything else:
| Field type | In a template |
|---|---|
text, slug | A string |
number, boolean | A number or a boolean. Format numbers with |number or |currency |
date, datetime | A date value for the date filters: |date, |datetime, |time, |iso, |relative |
select | An option, value and label, or a list of them |
portable_text | Rich text: print it with |ptext, as plain text with |plain, its headings with |toc |
relation | The related document as an entry (or a list), resolved one level deep. A target that is not published in the locale or not shown on this site is left out |
asset | A media value: url, alt, width, height and the rest, and img(), picture(), figure(), link(). See Images |
link | A link value: url, text and the rest, and link(), attributes(). See Links |
geo, address, phone, opening_hours, video, form, repeater, blocks, recurrence, geo_shape | See Fields |
engine-dmo describe fieldtypes lists every field type’s properties and helpers, with the keywords each helper takes. engine-dmo check refuses a property or helper a field’s type does not have.
Guides and collections
A guide or collection document’s entry also has:
| Path | Value |
|---|---|
entry.list | The list as entries, in order: a guide’s live items’ documents, or a collection’s page of documents |
entry.listTotal | How many documents the list holds in all |
entry.items | Guides: the live items, each document (an entry) and note (plain text) |
entry.pagination | Collections: the page of the list that ?page= asked for, shaped as an index page’s pagination. A page past the last answers 404 |
entry.jsonld | The page’s CollectionPage and ItemList structured data, which jsonld() merges into the page graph |
On a collection’s page pagination is also set at the top level of the context.
An index page
Rendered with <type>/index.html at the route’s index: address. The route’s paginate: sets the page size.
{% extends "base.html" %}
{% block body %}
<h1>{{ t('places.title') }}</h1>
<p>{{ t('index.count', pagination.total) }}</p>
<ul>
{% for item in items %}<li><a href="{{ item.uri|url }}">{{ item.title }}</a></li>{% endfor %}
</ul>
{% if pagination.pages > 1 %}
<nav aria-label="{{ t('pager.label') }}">
{% if pagination.prev %}<a rel="prev" href="{{ pagination.prev|url }}">{{ t('pager.previous') }}</a>{% endif %}
<span>{{ t('pager.page_of', 'page', pagination.page, 'pages', pagination.pages) }}</span>
{% if pagination.next %}<a rel="next" href="{{ pagination.next|url }}">{{ t('pager.next') }}</a>{% endif %}
</nav>
{% endif %}
{% endblock %} | Variable | Value |
|---|---|
items | The page of published documents of the type on this site in the request locale, in the type’s ordering (title order without one). Each is an entry with its relations resolved |
pagination.page | The current page, from ?page=, from 1 |
pagination.pages | How many pages |
pagination.total | How many documents match, exactly |
pagination.per | The page size, the route’s paginate: |
pagination.next, pagination.prev | The path of the next and previous page, keeping the selected facets; empty at the ends |
pagination.range | The page numbers from 1 to pages, as plain numbers with no address |
facets | One per field in the route’s facets: list |
page.index | true |
page.title | The type’s plural label, an admin word that also becomes the page’s <title>. Take the h1 from t() rather than printing this. An index has no title or description of its own |
page.noindex | true on a filtered page |
page.canonical | The unfiltered index (or, with engine-dmo, the most specific landing page the filters match) |
page.alternates | The index in each locale |
The response also carries Link headers with rel="prev" and rel="next".
Only pagination.prev and pagination.next are addresses. They keep the selected facets (/walks?difficulty=easy&page=2). There is no address for an arbitrary page number, and a link written as href="?page={{ n }}" drops every selected facet, which engine-dmo check does not report. Build the pager from prev and next, as above, or print numbered links only on an unfiltered index ({% if not page.noindex %}). The strings pager.label, pager.previous, pager.next and pager.page_of are your own, in config/strings/<locale>.yaml.
Each facet is field, label (the field’s label), selected (whether a value is chosen), clearUrl (the index without this facet) and values. Each value is value, label (an option’s or tag’s label in the request locale), count, selected and url (the index with the value chosen, or removed when it is the chosen one). A facet is selected by the query string ?<field>=<value>.
Facet values are links, not form controls, so draw them as lists of links under a heading inside one navigation landmark, not as a <fieldset> and not as one landmark per facet:
{% if facets %}
<nav aria-label="{{ t('facets.label') }}">
{% for facet in facets %}
<h2>{{ facet.label }}</h2>
<ul>
{% for v in facet.values %}<li><a href="{{ v.url|url }}"{% if v.selected %} aria-current="true"{% endif %}>{{ v.label }} ({{ v.count }})</a></li>{% endfor %}
</ul>
{% if facet.selected %}<p><a href="{{ facet.clearUrl|url }}">{{ t('facets.clear') }}</a></p>{% endif %}
{% endfor %}
</nav>
{% endif %} A select facet’s value labels are the option labels, which must be written as a map per locale (label: { en: Easy, cy: Hawdd }); a plain-string option label prints the value instead.
A landing page
engine-dmo
A landing page is an index filtered to a place and a category at a clean path. It renders <type>/landing.html where the chain has one, else <type>/index.html, with everything an index page has, plus landing (key, place, category, title, heading, description, content, faq, count, siblings, parents, breadcrumbs, jsonld and more) and page.landing set to true. See Landing pages.
The search page
Rendered with search.html at the site’s special.search route, for the request’s q, type, tag, sort, lat, lon, km and page parameters. The page is never cached.
| Variable | Value |
|---|---|
results | The search results, as search() returns them: items, total, facets, degraded, backend, query, sort, page, pages, hasMore, pagination, didYouMean. See Search in templates |
pagination | results.pagination |
page | uri, canonical, title (Search), type (search), isPreview |
Error pages
errors/404.html and errors/500.html receive the globals and the functions, with page set to uri (the requested path), title (Not found or Error), type (error) and isPreview. They receive no entry. Keep them simple: an error template that itself fails falls back to a plain-text response.
Block templates
{% blocks entry.body %} renders each item of a blocks field through blocks/<block key>.html on the site’s chain. The block template receives the page’s globals and functions, the page’s entry and page, and:
| Variable | Value |
|---|---|
block | The item: its _type (the block key), _key, and every field of the block by name, rendered as on an entry |
blockIndex | The item’s position, from 0 |
blockCount | How many items the field holds |
<aside class="callout callout--{{ block.tone.value }}">
{% if block.title %}<p class="callout__title">{{ block.title }}</p>{% endif %}
{{ block.content|ptext }}
</aside> engine-dmo check refuses a project that declares a block with no blocks/<key>.html anywhere on a site’s chain. engine-dmo new block <key> scaffolds one.
Rich text serialisers
|ptext renders an object block inside rich text through ptext/<set>/<block key>.html, where the set is the filter’s argument (default when none: |ptext or |ptext:"compact"). The serialiser receives the page’s globals and functions, and:
| Variable | Value |
|---|---|
node | The object block, with its fields by name |
index | Its position in the rich text, from 0 |
An object block with no serialiser renders nothing on a public page; under serve --dev it shows a notice naming the template to add, and engine-dmo check warns about it. Naming a set that has no templates at all is an error at render.
Endpoint and module templates
Declared endpoints (forms and their responses) and some modules render templates with context of their own, such as form, values, errors, submission, signals and endpoint. Those are described with the features: Forms, and, with engine-dmo, the member portal.
The template language
The Pongo2 syntax Meridian templates use (printing, expressions, filters, keyword arguments, inheritance, includes, macros, loops, whitespace) and the escaping and security rules the engine enforces on every template.
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.