Handbuch · 6

Templates – die Grundlagen

Seiten-HTML, Head, SEO-Titel, Beschreibung und Snippets sind Templates. Die Syntax entspricht weitgehend Twig bzw. Django (pongo2).

Ausgabe

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

Ausgaben werden automatisch HTML-escaped – < wird zu &lt;. Das schützt vor kaputtem Markup und eingeschleustem Code. Für HTML, das wirklich als HTML erscheinen soll:

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

Bedingungen und Schleifen

{% 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 %}

In Schleifen stehen forloop.Counter (ab 1), forloop.Counter0 (ab 0), forloop.First, forloop.Last und forloop.Revcounter bereit.

Variablen setzen

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

Kommentare

{# Wird nicht ausgegeben #}

Whitespace

Template-Tags verändern Leerzeichen und Zeilenumbrüche nicht – der Text wird exakt so ausgegeben, wie er dasteht (wie bei Twig). Hallo {% if name %}{{ name }}{% endif %}! behält das Leerzeichen nach „Hallo“.

Template-Syntax ausgeben: verbatim

Brauchst du {{ … }} wörtlich – etwa für Vue, Alpine oder Handlebars –, umschließe den Bereich:

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

Der Importer setzt verbatim automatisch, wenn das Original solche Zeichenfolgen enthält.

Verfügbare Variablen

Variable Inhalt
page.title, page.meta_title, page.slug, page.path, page.description, page.id, page.parent_id Aktuelle Seite (meta_title und description sind bereits ausgewertet)
page.noindex, page.nofollow Robots-Einstellungen der Seite (true/false)
page.lang Wirksame Sprache der Seite – eigene oder geerbte, sonst de (siehe Mehrsprachige Websites)
route.path Angefragter Pfad
route.slug Unterpfad bei Catch-All-Seiten, z. B. "mein-artikel" oder "2025/rueckblick"
route.rest Unterpfad als Liste: route.rest.0, route.rest.1 …
route.query.x Query-Parameter ?x=…
today Heutiges Datum als JJJJ-MM-TT – passend zu Datumsfeldern, z. B. data("events", "datum__gte", today) für kommende Termine
now Aktueller Zeitpunkt für den date-Filter: {{ now|date:"2006" }} (Jahr im Copyright)
site.title, site.id, site.domain, site.domains, site.favicon Die aktuelle Website (ihre Startseite) – site.domain ist die erste eingetragene Domain, site.favicon die URL des Website-Icons
preview true in der Admin-Vorschau

Funktionen

Funktion Ergebnis
nav() Veröffentlichte Unterseiten der Startseite der aktuellen Website (Hauptmenü) – ohne Seiten mit In Menüs ausblenden; nav(true) mit ihnen
children() / children(12) Veröffentlichte Unterseiten der aktuellen Seite bzw. von Seite 12 – ohne ausgeblendete; children(true) / children(12, true) mit ihnen
lang_root() / lang_root(12) Startseite des Sprachbaums der aktuellen Seite bzw. von Seite 12: die Seite selbst oder die nächste darüber mit eigener Sprache, sonst die Website-Startseite – für Menüs je Sprache: children(lang_root().id)
data("slug", …) Einträge einer Collection oder Ergebnis einer REST-Quelle (siehe Daten)
item("slug", "feld", wert) Erster Eintrag mit feld == wert – oder nichts
workflow("pfad", …) Ergebnis eines Workflows mit HTTP-Eingang unter diesem Pfad
image(id, sizes, klasse) Responsives <img> mit srcset, Maßen und Alt-Text – bei JPEG/PNG in <picture> mit WebP
file(id) Datei-Infos: .url, .name, .alt, .width, .height, .srcset, .webp_srcset, .mime, .size
asset_url(id) Versionierte URL einer CSS/JS-Datei
translations() / translations(eintrag) Veröffentlichte Sprachfassungen der Seite bzw. eines Eintrags, sortiert nach Sprachcode: je lang, title, url, current – für Sprachumschalter
language_name("fr") Name einer Sprache in ihr selbst („Français“, „Deutsch“ …), unbekannte Codes bleiben stehen
not_found() Beendet das Rendern mit der 404-Seite (Status 404)

Jede Seite aus nav()/children() (und lang_root()) hat title, path, slug, id, description und lang, dazu hidden (In Menüs ausblenden). Beispiel Footer mit Impressum und Datenschutz, die im Hauptmenü fehlen:

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

Nützliche Filter

Filter Beispiel
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 HTML ungefiltert ausgeben
markdown Markdown zu HTML (markdown:"html" lässt eingebettetes HTML durch – siehe unten)
json Wert als JSON, z. B. für JavaScript: <script>const cfg = {{ cfg|json }};</script>
urlencode <a href="/suche?q={{ q|urlencode }}">

Markdown direkt im HTML

Längere Texte schreibst du bequemer in Markdown – auch mitten im HTML einer Seite oder eines Snippets. Den Bereich mit {% filter markdown %} umschließen:

<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-Variablen und -Tags funktionieren innerhalb des Blocks.
  • Überschriften bekommen Anker für Sprunglinks, Umlaute werden umgeschrieben: „Über uns“ → #ueber-uns, „Größe“ → #groesse.
  • HTML im Markdown: Der normale Filter verwirft eingebettetes HTML (<div …>), weil er auch Inhalte aus Collections und REST-Quellen verarbeitet, die von außen kommen können. Für Texte, die du selbst im Editor schreibst, lässt {% filter markdown:"html" %} HTML durch – etwa für ein <div class="hinweis"> zwischen Absätzen. Für Daten von außen bleibt |markdown die sichere Wahl.

Pens – Spielwiesen

Unter Pens legst du kleine Spielwiesen an – wie bei CodePen: HTML (mit vollem Template-Umfang: data(), item(), workflow(), Filter …), CSS und JS nebeneinander, darunter das Ergebnis, live beim Tippen. Gut zum Ausprobieren von Templates mit echten Daten, für Prototypen oder kleine Microsites.

  • Jeder Pen hat eine eigene, nicht erratbare Adresse: /cms-admin/pen/<uuid>. Angemeldet öffnet sie den Editor (Vollbild), Strg+S speichert. Oben schaltest du das Layout um – Code oben (nebeneinander) oder Code seitlich (untereinander links, Ergebnis rechts) –, Vollbild zeigt nur das Ergebnis bildschirmfüllend (Esc beendet).
  • Öffentlich (Schalter oben): Ohne Anmeldung zeigt dieselbe Adresse das Ergebnis und daneben den Code – nur zum Lesen, in Reitern HTML/CSS/JS. Mit Code zeigen aus ist die Adresse eine eigenständige Seite: nur das Ergebnis, mit dem Titel des Pens – praktisch für schnelle Single Pages. Nicht öffentliche Pens gibt es für Besucher nicht (404). Als Admin siehst du die Besucher-Ansicht über Besucheransicht (neuer Tab, ?view=1).
  • Pens sind immer noindex, nofollow (Meta-Tag und Header X-Robots-Tag) und erscheinen in keiner Sitemap.
  • Das Ergebnis läuft in einer Sandbox mit eigenem Ursprung – Skripte im Pen kommen nicht an deine Admin-Sitzung.
  • Für Besucher werden nur veröffentlichte Collection-Einträge verwendet; im Editor siehst du wie in der Vorschau auch Entwürfe. site und nav() beziehen sich auf die Website der aufgerufenen Domain.
  • Pens sind Teil von Bundles; eine Versionshistorie haben sie nicht.