API-Referenz

Seitenbaukasten (Bausteine und Blöcke)

Seiten und Collection-Einträge können ihren Inhalt als Baum aus Blöcken speichern. Ein Block ist ein Baustein – ein Snippet mit Schema (Parameter mit Typen, Slots für Kind-Blöcke). Gespeichert werden nur Daten, nie HTML; gerendert wird über die Komponenten-Technik ({% component %}/{% slot %}) mit dem Tag {% blocks %}. Ein Blockbaum steckt an drei Stellen:

  • an Seiten im Feld blocks,
  • in Collection-Feldern vom Typ blocks,
  • in globalen Blöcken – wiederverwendbaren Bäumen, auf die andere Bäume per {"global": id} verweisen.

Baustein-Schema

Das Feld schema an einem Snippet macht es zum Baustein (null = kein Baustein; PUT ohne schema lässt es unverändert, "schema": null entfernt es):

{
  "block": true,
  "label": "Hero",
  "description": "Großes Titelbild mit Überschrift",
  "icon": "image",
  "category": "Inhalt",
  "params": [
    { "name": "titel", "label": "Titel", "type": "text", "required": true },
    { "name": "bild", "label": "Bild", "type": "image" },
    { "name": "text", "label": "Text", "type": "markdown" },
    { "name": "variante", "label": "Variante", "type": "select", "options": ["hell", "dunkel"], "default": "hell", "design": true },
    { "name": "produkt", "label": "Produkt", "type": "reference", "collection": 3 }
  ],
  "slots": [
    { "name": "default", "label": "Inhalt", "allow": ["text", "bild"] },
    { "name": "buttons", "label": "Buttons", "allow": ["button"], "max": 3 }
  ]
}
Feld Beschreibung
block true = im Formular-Editor als Baustein anbieten. Ein Schema mit false bleibt gespeichert, der Baustein ist aber nicht wählbar und Blöcke dieses Typs werden beim Speichern abgelehnt
label, description, icon, category Anzeige im Editor (label leer = Snippet-Name)
params[] Parameter wie Collection-Felder: name ([a-zA-Z_][a-zA-Z0-9_]*, eindeutig, nicht block, slots, id), label, type (alle Feldtypen außer blocks), required, options (bei select), collection (bei reference/references, muss existieren)
params[].default Standardwert (muss zum Typ passen): wird beim Rendern genutzt, wenn der Block den Parameter nicht setzt, und erfüllt beim Speichern ein Pflichtfeld
params[].design Design-Parameter (Variante, Abstände, Farben) – im CMS nur Anzeige (eigene Gruppe „Darstellung“); IllusionPublish nutzt es für Feldrechte
slots[] name (default = Standard-Slot {% slot %}), label, allow (erlaubte Bausteine als Snippet-Namen; fehlt/leer = alle), max (höchstens so viele Blöcke, 0 = beliebig)

Im Snippet stehen die Parameter als Variablen bereit ({{ titel }}), die Slots per {% slot "buttons" %}{% endslot %} (Standard-Slot {% slot %}), slots.<name> ist true, wenn ein Slot Inhalt hat, und block ist { "id": …, "type": … }.

Fehler beim Speichern: 400 mit den Codes block.paramNameInvalid, block.paramTypeInvalid, block.paramLinkMissing, block.paramDefaultInvalid, block.slotNameInvalid (siehe Fehlercodes). Warnungen lehnen nicht ab, sondern stehen in der Antwort von POST/PUT /snippets unter warnings (gleiches Format wie Fehler):

{ "id": 7, "name": "hero", "schema": { "block": true, "…": "…" },
  "warnings": [{ "code": "block.slotNotInContent", "error": "slot \"buttons\" is not used in the snippet ({% slot \"buttons\" %})", "params": { "slot": "buttons" } }] }

block.slotNotInContent – ein Slot des Schemas kommt im Snippet nicht als {% slot %} vor; block.slotNotInSchema – das Snippet nutzt einen Slot, den das Schema nicht kennt (nur bei block: true).

GET /blocks/types 🔑

Alle Bausteine (Snippets mit schema.block == true), sortiert nach Name – für Formular-Editor und Integrationen:

[{ "name": "hero", "label": "Hero", "description": "Großes Titelbild mit Überschrift", "icon": "image", "category": "Inhalt",
   "params": [{ "name": "titel", "label": "Titel", "type": "text", "required": true }], "slots": [{ "name": "buttons", "label": "Buttons", "allow": ["button"], "max": 3 }] }]

label leer = Snippet-Name, description leer = Beschreibung des Snippets; params/slots sind immer Listen.

Blockbaum

[
  { "id": "b7f3k2", "type": "hero", "params": { "titel": "Willkommen", "bild": 12, "variante": "dunkel" },
    "slots": { "buttons": [{ "id": "b9x1aa", "type": "button", "params": { "text": "Mehr", "link": "/ueber-uns" } }] } },
  { "id": "b2m8qq", "global": 4 }
]
Feld Beschreibung
id Im Baum eindeutig ([A-Za-z0-9_-]{1,64}), vom Client vergeben und über Speichervorgänge stabil. Der Server ergänzt fehlende und ersetzt ungültige oder doppelte (b + 6 Zeichen)
type Name eines Bausteins (Snippet-Name)
params Werte gemäß Schema: Bild/Datei = Datei-ID, reference = Eintrags-ID, references = Liste von IDs, date = YYYY-MM-DD, boolean, number, select = eine der options. Wird beim Speichern wie ein Collection-Eintrag umgewandelt ("3" → 3); unbekannte Parameter fallen weg. In der Antwort immer ein Objekt
slots Slot-Name → Liste von Blöcken (nur Slots mit Inhalt)
global Verweis auf einen globalen Block – dann ohne type/params/slots

Prüfung beim Speichern (Seiten, Einträge, globale Blöcke): Baustein existiert und ist einer; Pflichtparameter gesetzt (oder Standardwert); Werte passen zum Typ; Slots stehen im Schema; allow/max eingehalten; höchstens 10 Ebenen; höchstens 1 MB (JSON); verwiesene globale Blöcke existieren. Ein Verweis {"global": id} in einem Slot mit allow-Liste ist nur erlaubt, wenn alle Blöcke der obersten Ebene des globalen Blocks erlaubte Bausteine sind (globale Verweise auf dessen oberster Ebene werden aufgelöst), sonst block.globalNotAllowed; für max zählt ein Verweis als ein Block. Fehler: 400 mit Code und dem Pfad im Baum, z. B. { "code": "block.slotNotAllowed", "params": { "path": "0.slots.buttons.1", "slot": "buttons", "type": "text" } }. Bei Collection-Feldern kommt params.field dazu.

Geprüft wird nur, was mitgeschickt wird: PUT /pages/{id} ohne blocks bzw. ein Teil-Update eines Eintrags ohne das Blöcke-Feld lässt den gespeicherten Baum ungeprüft stehen – auch wenn ein Baustein inzwischen gelöscht oder umbenannt wurde. Gerendert wird so ein Block dann gar nicht (Live) bzw. als Hinweis (Vorschau).

Ausgabe im Template

{% blocks ausdruck %} rendert einen Blockbaum: {% blocks page.blocks %} (Seite) oder {% blocks post.inhalt %} (Feld eines Eintrags aus data()/item()). Je Block wird der Baustein wie eine Komponente gerendert – mit den globalen Variablen (page, site, route, data() …), den Parametern und den gerenderten Kind-Blöcken als Slots. Parameterwerte sind typgerecht aufbereitet:

Typ Im Baustein
text, textarea, url, select, date, number, boolean Rohwert (wird wie üblich escaped)
markdown Gerendertes, sicheres HTML (wie |markdown)
html Sicheres HTML
image, file Datei-Objekt wie file(id) (id, url, srcset, webp_srcset, width, height, alt, name, private …), z. B. {{ image(bild.id) }} oder {{ bild.url }}. Private Dateien nur in der Vorschau signiert
reference, references Aufgelöster Eintrag bzw. Liste (eine Ebene, wie bei Collection-Verknüpfungen); für Besucher nur veröffentlichte

Leere Parameter bekommen den default des Schemas. Ein leerer oder ungültiger Wert hinter {% blocks %} gibt nichts aus. Unbekannte Bausteine (gelöscht, kein Baustein mehr) und fehlende globale Blöcke ergeben live keine Ausgabe (mit Log-Eintrag), in der Vorschau einen sichtbaren Hinweis-Kasten (<div class="ic-block-problem">). CSS/JS, die an einem Baustein hängen, werden wie bei Komponenten auf Seiten geladen, die ihn benutzen.

Vorschau-Markierungen

Nur in Vorschauen (Admin-Vorschau POST /pages/{id}/preview, ?preview=1 mit Admin-Session, Pen-Editor) umschließen HTML-Kommentare jeden Block – für Integrationen wie IllusionPublish, die Blöcke in der Vorschau anklickbar machen. Es gibt bewusst keine Wrapper-Elemente (sie würden CSS wie .grid > .card brechen), und Live-Seiten, Vorschau-Links und der statische Export enthalten nie Markierungen.

<!--ic-block:page.blocks/0:b7f3k2:hero--><section class="hero">…
  <!--ic-block:page.blocks/0/slots/buttons/0:b9x1aa:button--><a …>Mehr</a><!--/ic-block:b9x1aa-->
</section><!--/ic-block:b7f3k2-->
<!--ic-block:page.blocks/1:b2m8qq:global:4--><!--ic-block:global:4/0:n1:text-->…<!--/ic-block:n1--><!--/ic-block:b2m8qq-->

Format: <!--ic-block:<pfad>:<id>:<type>--> … <!--/ic-block:<id>-->, für Verweise auf globale Blöcke <!--ic-block:<pfad>:<id>:global:<gid>-->. Der Pfad beginnt mit der Herkunft des Baums – page.blocks, item:<collection-slug>:<eintrags-id>:<feld> (Blöcke-Feld eines Eintrags, auch beim ungespeicherten item der Vorschau) oder global:<gid> (Kinder eines globalen Blocks) –, gefolgt von /<index> und /slots/<name>/<index> je Ebene. Pfade enthalten Doppelpunkte; id und type sind die letzten beiden Teile (bei globalen Blöcken die letzten drei). Wird ein Baum ohne erkennbare Herkunft ausgegeben (z. B. ein zusammengesetzter Wert), beginnt der Pfad mit blocks.

Listen, Slots und Feld-Markierungen

Ebenfalls nur in Vorschauen kommen drei weitere Angaben dazu – für visuelle Editoren wie IllusionPublish, die Inhalte direkt auf der Seite bearbeiten:

<!--ic-blocks:page.blocks-->…<!--/ic-blocks:page.blocks-->
<!--ic-slot:page.blocks/0/slots/buttons-->…<!--/ic-slot:page.blocks/0/slots/buttons-->
<h1 data-ic-field="page.blocks/0/titel">Frisch aus dem Ofen</h1>
  • Listen: Jede Ausgabe von {% blocks … %} steht zwischen <!--ic-blocks:<herkunft>--> und <!--/ic-blocks:<herkunft>--> – auch eine leere Liste, damit ein Editor dort einen Einfügepunkt zeigen kann. Herkunft wie beim Pfad (page.blocks, item:<collection-slug>:<eintrags-id>:<feld>, unbekannt blocks).
  • Slots: Jeder {% slot %} eines Blocks steht zwischen <!--ic-slot:<blockpfad>/slots/<name>--> und <!--/ic-slot:…-->, ebenfalls auch leer (bzw. mit seinem Fallback-Inhalt). Steht der Slot im Template in einem {% if slots.x %}, gibt es für den leeren Slot keine Markierung.
  • Felder: data-ic-field="<blockpfad>/<parameter>" an dem Element, das einen Parameter darstellt. Automatisch für Parameter vom Typ text, textarea und markdown, wenn ihr Wert unverändert den ganzen Inhalt eines Elements bildet (<h1>{{ titel }}</h1>, <div class="text">{{ inhalt }}</div>, Zeilenumbrüche per |linebreaksbr sind erlaubt), und für Bild-Parameter am <img> von image(bild.id) bzw. am Element, in dessen Attributen {{ bild.url }} steht (src, style …). Werte in Attributen, Skripten, mitten in anderem Text oder durch Filter verändert (|upper, |truncatechars) bleiben unmarkiert – geraten wird nie, und Attribute bleiben unverändert. Explizit markiert {{ block_field("titel") }} ein Element (gibt in der Vorschau data-ic-field="…" aus, live nichts): <p class="lead"{{ block_field("titel") }}>{{ titel|upper }}</p>.

Technik: Die Automatik umschließt die Werte beim Rendern mit unsichtbaren Zeichen aus einem privaten Unicode-Bereich (U+F0000 ff.) und entfernt sie nach dem Rendern wieder aus dem ganzen Dokument (auch URL-kodiert und als JS-Escape). Filter, die Zeichen zählen (|length, |truncatechars), zählen diese Zeichen in der Vorschau mit – das Ergebnis kann dort etwas kürzer ausfallen als live.

Globale Blöcke

Wiederverwendbare Blockbäume (z. B. Newsletter-Kasten, Kontaktleiste). Ein Verweis {"global": id} setzt beim Rendern den aktuellen Baum des globalen Blocks ein – Änderungen wirken überall. „Einbetten“ (lokale Kopie) macht der Client: Verweis durch eine Kopie der Blöcke mit neuen IDs ersetzen.

{ "id": 4, "name": "Newsletter-Kasten", "folder": "Kästen", "description": "Anmeldung",
  "blocks": [{ "id": "n1", "type": "text", "params": { "inhalt": "Jetzt anmelden" } }],
  "created_at": 1791191665, "updated_at": 1791191700 }
Endpunkt Zugriff Beschreibung
GET /global-blocks 🔑 Alle globalen Blöcke (mit blocks), sortiert nach Ordner und Name
POST /global-blocks ✏️ Anlegen; Pflicht: name. 201
GET /global-blocks/{id} 🔑 Ein globaler Block
PUT /global-blocks/{id} ✏️ Merge-Update mit Konfliktprüfung (base_updated_at); ohne blocks bleibt der Baum (ungeprüft). Versionshistorie (kind=globalblock)
DELETE /global-blocks/{id} ✏️ Nur ohne Verwendung, sonst 409 block.globalInUse mit params.count. 204
GET /global-blocks/{id}/usage 🔑 Verwendungen (direkte Verweise)

Verwendungen: [{ "kind": "page", "id": 5, "title": "Aktuelles", "path": "/aktuelles" }, { "kind": "item", "id": 12, "title": "Sauerteig", "path": "/blog/sauerteig", "collection": "blog" }, { "kind": "global", "id": 2, "title": "Fußbereich", "path": "" }] – gesucht wird in den Blockbäumen aller Seiten, aller Einträge mit Blöcke-Feldern und der anderen globalen Blöcke (kein Index, bei jedem Aufruf). title bei Einträgen: Feld title/titel/name oder erstes Textfeld; path nur, wenn die Collection eine Detailseite hat.

Überschreibungen je Verwendung: Ein Verweis darf zusätzlich "overrides": { "<Block-ID im globalen Block>": { "<Parameter>": <Wert> } } tragen – einzelne Parameter der Blöcke im globalen Baum werden nur für diese Verwendung ersetzt (Parameter, nie Aufbau oder Slots). Werte werden wie Baustein-Parameter geprüft (block.paramInvalid, params.path = <pfad>.overrides.<id>); null leert den Parameter (es gilt der Standard des Bausteins). Blöcke und Parameter, die im globalen Block nicht (mehr) vorkommen, werden beim Speichern verworfen und beim Rendern ignoriert. Datei-IDs in Überschreibungen zählen als verwendet. Änderungen am globalen Block wirken weiter überall, soweit nicht überschrieben.

Zyklen: Ein globaler Block darf sich weder direkt noch über andere globale Blöcke selbst enthalten – beim Speichern 400 block.globalCycle; beim Rendern bricht ein Zyklus (z. B. nach einem Wiederherstellen) mit einem Hinweis ab.

Eingeschränkte Slots: Würde ein PUT mit neuem blocks einen globalen Block dort unzulässig machen, wo er bereits in einem Slot mit allow-Liste steckt (auch über andere globale Blöcke), lehnt der Server ab: 400 block.globalNowNotAllowed mit params.count (betroffene Stellen).

Globale Blöcke stehen im Bundle (Tabelle global_blocks, IDs bleiben). Datei-IDs in Bild-/Datei-Parametern aller Blockbäume zählen beim Aufräumen als verwendet.

Fehlercodes des Seitenbaukastens

Code Parameter Bedeutung
block.treeInvalid path Kein Blockbaum (keine Liste, Block kein Objekt, params/slots falsch, global keine ID)
block.fieldInvalid field Collection-Feld vom Typ blocks ist keine Liste (auch kein JSON-Text)
block.typeMissing path Block ohne type
block.typeUnknown path, type Snippet gibt es nicht
block.notABlock path, type Snippet ist kein Baustein (schema.block fehlt)
block.paramRequired path, param Pflichtparameter fehlt (ohne Standardwert)
block.paramInvalid path, param, error Wert passt nicht zum Typ
block.slotUnknown path, slot, type Slot steht nicht im Schema
block.slotNotAllowed path, slot, type Baustein im Slot nicht erlaubt (allow)
block.slotMax path, slot, max Zu viele Blöcke im Slot
block.tooDeep path, max Mehr als 10 Ebenen
block.tooLarge max Baum größer als 1 MB
block.globalUnknown path, id Globaler Block existiert nicht
block.globalCycle id Globaler Block enthielte sich selbst
block.globalNotAllowed path, slot, id, type Verweis auf einen globalen Block in einem Slot mit allow-Liste, dessen oberste Ebene einen nicht erlaubten Baustein type enthält
block.globalNowNotAllowed count Änderung eines globalen Blocks würde ihn an count Verwendungen in eingeschränkten Slots unzulässig machen
block.globalInUse count Löschen eines verwendeten globalen Blocks
block.paramNameInvalid, block.paramTypeInvalid, block.paramLinkMissing, block.paramDefaultInvalid, block.slotNameInvalid name (type) Fehler im Baustein-Schema
block.slotNotInContent, block.slotNotInSchema slot Nur Warnungen (warnings) beim Speichern eines Snippets

path ist der Pfad im Baum mit Punkten (0.slots.buttons.1, leer = der ganze Baum); bei Collection-Feldern zusätzlich field.