Manual · 6

Templates – the basics

Page HTML, Head, SEO title, description and Snippets are templates. The syntax largely corresponds to Twig or Django (pongo2).

Output

{{ page.title }}
{{ post.titel|upper }}
{{ preis|floatformat:2 }} €
{{ beschreibung|default:"Keine Beschreibung" }}

Output is automatically HTML-escaped – < becomes &lt;. This protects against broken markup and injected code. For HTML that really should appear as HTML:

  • HTML fields: {{ post.inhalt|safe }}
  • Markdown fields: {{ post.text|markdown }}

Conditions and loops

{% if page.slug == "kontakt" %}
  …
{% elif route.query.seite %}
  …
{% else %}
  …
{% endif %}

{% for p in data("team") %}
  <li class="{% if forloop.First %}erste{% endif %}">{{ forloop.Counter }}. {{ p.name }}</li>
{% empty %}
  <li>Noch niemand da.</li>
{% endfor %}

Inside loops, forloop.Counter (from 1), forloop.Counter0 (from 0), forloop.First, forloop.Last and forloop.Revcounter are available.

Setting variables

{% with post=item("blog", "slug", route.slug) %}
  <h1>{{ post.titel }}</h1>
{% endwith %}

Comments

{# Not output #}

Whitespace

Template tags don’t change spaces and line breaks – the text is output exactly as it stands (as with Twig). Hallo {% if name %}{{ name }}{% endif %}! keeps the space after “Hallo”.

Outputting template syntax: verbatim

If you need {{ … }} literally – for Vue, Alpine or Handlebars, for example –, wrap the area:

{% verbatim %}<div id="app">{{ nachricht }}</div>{% endverbatim %}

The importer sets verbatim automatically if the original contains such character sequences.

Available variables

Variable Content
page.title, page.meta_title, page.slug, page.path, page.description, page.id, page.parent_id Current page (meta_title and description are already evaluated)
page.noindex, page.nofollow Robots settings of the page (true/false)
page.lang Effective language of the page – its own or inherited, otherwise de (see Multilingual websites)
route.path Requested path
route.slug Subpath on Catch-All pages, e.g. "mein-artikel" or "2025/rueckblick"
route.rest Subpath as a list: route.rest.0, route.rest.1 …
route.query.x Query parameter ?x=…
today Today’s date as YYYY-MM-DD – matching date fields, e.g. data("events", "datum__gte", today) for upcoming events
now Current point in time for the date filter: {{ now|date:"2006" }} (year in the copyright)
site.title, site.id, site.domain, site.domains, site.favicon The current website (its home page) – site.domain is the first domain entered, site.favicon the URL of the website icon
preview true in the admin preview

Functions

Function Result
nav() Published subpages of the current website’s home page (main menu) – without pages with Hide in menus; nav(true) with them
children() / children(12) Published subpages of the current page or of page 12 – without hidden ones; children(true) / children(12, true) with them
lang_root() / lang_root(12) Home page of the language tree of the current page or of page 12: the page itself or the nearest one above with its own language, otherwise the website’s home page – for menus per language: children(lang_root().id)
data("slug", …) Entries of a Collection or result of a REST source (see Data)
item("slug", "feld", wert) First entry with feld == wert – or nothing
workflow("pfad", …) Result of a Workflow with an HTTP input at this path
image(id, sizes, klasse) Responsive <img> with srcset, dimensions and alt text – for JPEG/PNG inside <picture> with WebP
file(id) File info: .url, .name, .alt, .width, .height, .srcset, .webp_srcset, .mime, .size
asset_url(id) Versioned URL of a CSS/JS file
translations() / translations(entry) Published language versions of the page or of an entry, sorted by language code: each with lang, title, url, current – for language switchers
language_name("fr") Name of a language in that language (“Français”, “Deutsch” …); unknown codes stay as they are
not_found() Ends rendering with the 404 page (status 404)

Every page from nav()/children() (and lang_root()) has title, path, slug, id, description and lang, plus hidden (Hide in menus). Example footer with legal notice and privacy policy, which are missing from the main menu:

<footer>{% for p in nav(true) %}{% if p.hidden %}<a href="{{ p.path }}">{{ p.title }}</a> {% endif %}{% endfor %}</footer>

Useful filters

Filter Example
default {{ x|default:"–" }}
upper, lower, title, capfirst {{ name|upper }}
truncatechars, truncatewords {{ text|truncatechars:120 }}
floatformat {{ preis|floatformat:2 }}
length {{ liste|length }}
join {{ tags|join:", " }}
safe Output HTML unfiltered
markdown Markdown to HTML (markdown:"html" lets embedded HTML through – see below)
json Value as JSON, e.g. for JavaScript: <script>const cfg = {{ cfg|json }};</script>
urlencode <a href="/suche?q={{ q|urlencode }}">

Markdown directly in HTML

Longer texts are more convenient to write in Markdown – even in the middle of a page’s or Snippet’s HTML. Wrap the area with {% filter markdown %}:

<main class="container">
{% filter markdown %}
# Über uns

Wir gestalten und entwickeln **Websites mit Haltung** – von der Idee bis zum Betrieb.
Mehr dazu unter [Leistungen](/leistungen) – das Team von {{ site.title }} freut sich auf dich.

- Beratung
- Umsetzung
{% endfilter %}
</main>
  • Template variables and tags work inside the block.
  • Headings get anchors for jump links, umlauts are transliterated: “Über uns” → #ueber-uns, “Größe” → #groesse.
  • HTML in Markdown: The normal filter discards embedded HTML (<div …>), because it also processes content from Collections and REST sources, which can come from outside. For texts you write yourself in the editor, {% filter markdown:"html" %} lets HTML through – for example for a <div class="hinweis"> between paragraphs. For data from outside, |markdown remains the safe choice.

Pens – playgrounds

Under Pens you create small playgrounds – like on CodePen: HTML (with the full template feature set: data(), item(), workflow(), filters …), CSS and JS side by side, with the result below, live as you type. Good for trying out templates with real data, for prototypes or small microsites.

  • Each Pen has its own address that can’t be guessed: /cms-admin/pen/<uuid>. When signed in, it opens the editor (fullscreen); Ctrl+S saves. At the top you switch the layout – Code on top (side by side) or Code at side (stacked on the left, result on the right) –; Fullscreen shows only the result, filling the screen (Esc exits).
  • Public (switch at the top): Without signing in, the same address shows the result and the code next to it – read-only, in HTML/CSS/JS tabs. With Show code off, the address is a standalone page: only the result, with the Pen’s title – handy for quick single pages. Non-public Pens don’t exist for visitors (404). As an admin, you see the visitor view via Visitor view (new tab, ?view=1).
  • Pens are always noindex, nofollow (meta tag and X-Robots-Tag header) and don’t appear in any sitemap.
  • The result runs in a sandbox with its own origin – scripts in the Pen can’t get at your admin session.
  • For visitors, only published Collection entries are used; in the editor, as in the preview, you also see drafts. site and nav() refer to the website of the requested domain.
  • Pens are part of bundles; they don’t have a version history.