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 <. 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|markdowndie 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 HeaderX-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.
siteundnav()beziehen sich auf die Website der aufgerufenen Domain. - Pens sind Teil von Bundles; eine Versionshistorie haben sie nicht.