Variables and context
Every object Quilium injects into a Liquid template at render time, what each one holds, and how to read it.
Quilium renders Liquid server-side. At render time the engine injects a fixed set of context objects. A template reads them; it never fetches data itself. Dynamic data comes from queries declared on the page-type (route-aware, exposed as page.custom.<key>) and from content-block fields (content.custom.<field>).
Context objects
All field, section, and query keys are camelCase. Query results are always arrays, even at limit: 1; guard with and <expr>.size > 0 before indexing.
| Object | Access | Holds |
|---|---|---|
page |
page.contents['<section>'], page.custom.<queryKey>, page.metas.{title,description,robots}, page.title, page.id |
The current page. page.contents['main'] is the array of blocks dropped in the main section. |
page.custom |
page.custom.<queryKey> (array) |
Route-aware query results declared on the page-type. Each key is one query field. Always an array, so guard before [0]. |
content |
content.custom.<field>, content.custom.<query>, content.id, content.template, content.form.<field>.errors |
Inside an element template: the current content block. content.id is the block id (form target value); content.template is its element path. |
content.custom |
content.custom.<field> |
The block's own fields. Idiom: {% assign c = content.custom %} then c.title. A content-type may carry its own query, read at content.custom.<query>. |
content.form |
content.form.<field>.errors |
Per-field validation errors after a POST, as an object {rule: message}. Empty before submission. |
globals |
globals.<fieldKey> |
Global fields, flat: the tabset key is dropped from the path. Schema tabsets.infos.fields.copyright reads as globals.copyright, never globals.infos.copyright. Field keys must be unique across all tabsets. |
request |
request.body.<field>, request.query, request.path, request.cookies |
The incoming request. request.body.<field> holds submitted form values, used to prefill on re-render. |
request.body |
request.body.<field> |
Posted form values, keyed by field name. |
route |
route.<param> |
QUERY-ONLY. Valid only inside a page-type query config (e.g. where: { '&=': { slug: '{{route.article.slug}}' } }). It does not exist in a template. Templates read the resolved result at page.custom.<key>. See Routing and URLs. |
query |
query.page |
QUERY-ONLY. The URL query string inside a query config. {{query.page}} substitutes (pagination); {{query.limit}} does not substitute. |
now |
now, now.year, now.month, now.day, now.hour, now.datetime, now.timestamp, now.timezone |
Server time. Format with `{{ now |
locale |
locale.slug |
The current locale. Use for <html lang="{{ locale.slug }}">. See the multilingual model. |
locales |
loc.slug, loc.url (per item) |
Every locale. loc.url is this page in that locale. Iterate for a language switcher. |
sitemap |
sitemap.<nav>.children → item.url, item.navTitle, item.active |
Navigation trees. item.url is already locale-aware; do not re-prefix it. |
query configuration. A template that reads route.* or query.* directly gets nothing. The page-type resolves {{route.article.slug}} into page.custom.article; the template reads page.custom.article[0].Reading a query result
Queries are arrays. Always test size before indexing.
{% assign source = page.custom.latest %}
{% if source and source.size > 0 %}
{% for a in source %}{% include '_articleCard', art: a %}{% endfor %}
{% else %}
<p>{{ 'Aucun article pour le moment.' | _ }}</p>
{% endif %}
A current-item join (limit 1) reads index [0] behind the same guard:
{% if page.custom.author and page.custom.author.size > 0 %}
{% assign au = page.custom.author[0] %}
<h2>{{ au.nom }}</h2>
{% endif %}
Function-form helpers are inert in Liquid
The render payload also carries function-form helpers, url, redirect, responseCode, setCookie, and _. They are leftovers from the engine that preceded Liquid and are inert in Liquid. In a Liquid template you use the filter forms instead ('article' | url: …, 'msgid' | _). See Liquid filters for every filter signature.
copyright under tabset infos is globals.copyright. Because of this, every global field key must be unique across all tabsets, or one shadows the other.Last updated September 6, 2026.