All Systems Operational
Powered By
profound-logo
profound-logoProfound CMS
⌘K
Admin

Skripterstellung im Template-Builder

Ein praktischer Leitfaden zum Schreiben von CEL-Ausdrücken im CMS.

Continue Reading
Previous‹Statisches Rendering mit Edit-Modus-UnterstützungNextCreate Profound Next›

Hybrid

Renderer-ProjektParametrisches RoutingKomponententypenSseAdmin Panel Proxy EinrichtenStatisches Rendering mit Edit-Modus-UnterstützungSkripterstellung im Template-BuilderCreate Profound Next

Kopflos

SchnellstartJson Und Claude CodeKomponente-Zod-Abruf

Mcp

Mcp

Cms Funktionen

Feat Docs TemplateFeat Vorlagen BuilderFeat TranslatorFeature-Organisation

Motivation

Unser Ansatz

Terminologie

Hybrid vs. Headless

Ein praktischer Leitfaden zum Schreiben von CEL-Ausdrücken im CMS.


Funktionsweise von CEL

CEL (Common Expression Language) ist eine leichtgewichtige Skriptsprache, die in unser CMS integriert ist. Damit lassen sich dynamische Ausdrücke schreiben, die Daten aus Dokumenten ziehen, URL-Parameter lesen und Werte spontan berechnen können.

So läuft ein CEL-Skript ab:

Ihr Skript                     Die Engine                      Ergebnis
    |                              |                              |
    v                              v                              v
documents.get("article", "intro") --> Ruft aus der Datenbank ab --> { headline: "Willkommen", body: "..." }
         .headline                --> Extrahiert das Feld        --> "Willkommen"

Betrachten Sie CEL als eine schreibgeschützte Abfragesprache. Sie kann nichts in der Datenbank verändern – sie liest nur Daten aus und gibt ein berechnetes Ergebnis zurück. Damit ist sie überall im CMS sicher einsetzbar.


Die Bausteine

Jeder CEL-Ausdruck hat Zugriff auf drei Dinge:

ObjektWas es istBeispiel
documentsRuft jedes Dokument aus dem CMS abdocuments.get("country", "us")
metaInformationen über die aktuelle Anfrage (Locale, URL-Parameter)meta.locale, meta.params.slug
schemaFelddefinitionen des aktuellen Dokumentsschema.fields

Selbstreferenz mit doc

Wenn Sie CEL-Ausdrücke im Dokumenteneditor schreiben, können Sie über das Objekt doc auf die Feldwerte des aktuellen Dokuments zugreifen. Das ermöglicht berechnete Felder und Feldübergreifende Verweise.

// Zugriff auf das Preisfeld des aktuellen Dokuments
doc.price

// Gesamtbetrag aus Feldern des aktuellen Dokuments berechnen
doc.price * doc.quantity

// Bedingung basierend auf dem Status des aktuellen Dokuments
doc.status == "published" ? doc.title : "Entwurf: " + doc.title

Das Objekt doc enthält alle Feldwerte des bearbeiteten Dokuments. Das ist hilfreich für:

  • Berechnete Felder (z. B. doc.price * doc.quantity)
  • Bedingte Darstellung basierend auf dem Dokumentstatus
  • Validierungsähnliche Ausdrücke

Dokumente abrufen

Die stärkste Funktion von CEL ist das Abrufen von Dokumenten aus dem gesamten CMS.

Ein einzelnes Dokument abrufen

Syntax: documents.get(schemaName, identifier)

Angenommen, Sie haben ein article-Dokument mit dem Bezeichner "welcome-post" gespeichert:

// Im CMS gespeichert als: article / welcome-post
{
  "headline": "Willkommen auf unserer Plattform",
  "author": "Sarah Chen",
  "body": "Wir freuen uns, bekannt zu geben...",
  "tags": ["ankündigung", "nachrichten"]
}

So holen Sie das gesamte Dokument:

documents.get("article", "welcome-post")

Ergebnis:

{
  "headline": "Willkommen auf unserer Plattform",
  "author": "Sarah Chen",
  "body": "Wir freuen uns, bekannt zu geben...",
  "tags": ["ankündigung", "nachrichten"]
}

Nur die Überschrift holen:

documents.get("article", "welcome-post").headline

Ergebnis: "Willkommen auf unserer Plattform"

Die Autorin abrufen:

documents.get("article", "welcome-post").author

Ergebnis: "Sarah Chen"


URL-Parameter verwenden

Wenn Ihre Seite dynamische Routen hat (z. B. /articles/[slug]), können Sie meta.params nutzen, um den URL-Parameter abzurufen und das richtige Dokument zu holen.

Besucht jemand /articles/welcome-post:

documents.get("article", meta.params.slug).headline

Ergebnis: "Willkommen auf unserer Plattform"

So erstellen Sie dynamische Seiten – dasselbe CEL-Skript funktioniert für jeden Artikel und verwendet einfach den Slug aus der URL.


Mehrere Dokumente abrufen

Syntax: documents.find(schemaName) oder documents.find(schemaName, filter)

// Alle Länder abrufen
documents.find("country")

Ergebnis:

[
  { "code": "us", "name": "Vereinigte Staaten", "flag": "US" },
  { "code": "sa", "name": "Saudi-Arabien", "flag": "SA" },
  { "code": "gb", "name": "Vereinigtes Königreich", "flag": "GB" }
// Länder mit Filter abrufen
documents.find("country", { "where": { "code": "us" } })

Ergebnis:

[
  { "code": "us", "name": "Vereinigte Staaten", "flag": "US" }
]

Übersetzungen

CEL unterstützt das Abrufen übersetzter Dokumentinhalte auf zwei Arten: automatische, locale-basierte Übersetzung und explizite Übersetzungssuche.

Automatische Übersetzung über meta.locale

Wenn meta.locale gesetzt ist (z. B. über Routenparameter oder Benutzerpräferenzen), fügt documents.get() automatisch übersetzte Inhalte zusammen:

// Wenn meta.locale "fr" ist, wird die französische Übersetzung mit dem Basisdokument zusammengeführt
documents.get("greeting", "welcome").headline

So funktioniert es:

  1. Basisdokument wird abgerufen
  2. Wenn meta.locale nicht "en" oder "en-US" ist, wird die Übersetzung in der Tabelle translations gesucht
  3. Übersetzte Felder werden über den Basiskontext gemergt: { ...baseContent, ...translatedContent }

Damit überschreiben übersetzte Felder die Basisfelder, während nicht übersetzte Felder auf das Basisdokument zurückfallen.

Explizite Übersetzung mit documents.translated()

Für Fälle, in denen Sie eine bestimmte Übersetzung unabhängig von der aktuellen Locale abrufen müssen:

Syntax: documents.translated(schemaName, identifier, locale)

// Immer die spanische Übersetzung abrufen
documents.translated("greeting", "welcome", "es").headline

// Übersetzung basierend auf URL-Parametern abrufen
documents.translated("product", meta.params.id, meta.params.lang).description

// Übersetzungen vergleichen
documents.translated("article", "intro", "en").title + " / " + documents.translated("article", "intro", "fr").title

Übersetzungsbeispiel

Ihre Begrüßungsdokumente mit Übersetzungen:

// Basisdokument: greeting / welcome
{ "headline": "Willkommen", "subheadline": "Willkommen auf unserer Plattform" }

// Übersetzung (Sprache: "fr")
{ "headline": "Bienvenue", "subheadline": "Bienvenue sur notre plateforme" }

// Übersetzung (Sprache: "es")
{ "headline": "Bienvenido", "subheadline": "Bienvenido a nuestra plataforma" }

CEL-Skripte:

// Mit meta.locale = "fr"
documents.get("greeting", "welcome").headline
// Ergebnis: "Bienvenue"

// Explizite spanische Übersetzung
documents.translated("greeting", "welcome", "es").headline
// Ergebnis: "Bienvenido"

// Fallback-Muster für fehlende Übersetzungen
documents.translated("greeting", "welcome", meta.params.lang) != null
  ? documents.translated("greeting", "welcome", meta.params.lang).headline
  : documents.get("greeting", "welcome").headline

Praxisbeispiele

Beispiel 1: Hero-Block-Titel aus einem anderen Dokument

Sie haben einen hero-block, der eine Überschrift aus einem article-Dokument anzeigen soll.

Ihr Artikeldokument (Bezeichner: "homepage-hero"):

{
  "headline": "Schneller entwickeln, smarter ausliefern",
  "subheadline": "Das moderne CMS für Entwicklerinnen und Entwickler"
}

CEL-Skript im Titelfeld des Hero-Blocks:

documents.get("article", "homepage-hero").headline

Ergebnis: Der Hero zeigt "Schneller entwickeln, smarter ausliefern"


Beispiel 2: Landesname anhand des Codes

Sie erstellen eine Seite unter /countries/[code] und möchten den vollständigen Ländernamen anzeigen.

Ihre Länderdokumente:

// country / us
{ "code": "us", "name": "Vereinigte Staaten", "flag": "US", "languages": ["en", "es"] }

// country / sa
{ "code": "sa", "name": "Saudi-Arabien", "flag": "SA", "languages": ["ar", "en"

CEL-Skript:

documents.get("country", meta.params.code).name

Wenn jemand /countries/us besucht:

  • meta.params.code = "us"
  • Ergebnis: "Vereinigte Staaten"

Wenn jemand /countries/sa besucht:

  • meta.params.code = "sa"
  • Ergebnis: "Saudi-Arabien"

Beispiel 3: Bedingte Inhalte basierend auf der Locale

Zeigen Sie unterschiedliche Überschriften abhängig von der Locale der Nutzerin oder des Nutzers.

meta.locale == "ar-SA" ? "Willkommen, alle zusammen" : "Willkommen"

Wenn die Locale "ar-SA" ist: Ergebnis "Willkommen, alle zusammen" Wenn die Locale etwas anderes ist: Ergebnis "Willkommen"


Beispiel 4: Verkettete Dokumentabrufe

Ihr article enthält ein Feld countryCode, und Sie möchten den vollen Ländernamen abrufen.

Artikeldokument:

{ "headline": "Neuigkeiten aus den USA", "countryCode": "us" }

CEL-Skript:

documents.get("country", documents.get("article", "us-news").countryCode).name

Was passiert:

  1. documents.get("article", "us-news") gibt { "headline": "Neuigkeiten aus den USA", "countryCode": "us" } zurück
  2. .countryCode extrahiert "us"
  3. documents.get("country", "us") gibt { "code": "us", "name": "Vereinigte Staaten", ... } zurück
  4. .name extrahiert "Vereinigte Staaten"

Ergebnis: "Vereinigte Staaten"


Beispiel 5: Fallback-Werte

Wenn ein Dokument möglicherweise nicht existiert, können Sie einen Fallback angeben:

documents.get("article", meta.params.slug) != null
  ? documents.get("article", meta.params.slug).headline
  : "Artikel nicht gefunden"

Oder prüfen, ob ein bestimmtes Feld existiert:

documents.get("article", "intro").author != null
  ? documents.get("article", "intro").author
  : "Unbekannte Autorin"

Beispiel 6: Mit Listen arbeiten

Ihr Artikel hat Tags und Sie möchten prüfen, ob ein bestimmter Tag vorhanden ist:

"featured" in documents.get("article", "welcome-post").tags

Ergebnis: true, wenn der Artikel den Tag "featured" hat

Den ersten Tag holen:

documents.get("article", "welcome-post").tags[0]

Ergebnis: "ankündigung" (der erste Tag)

Tags zählen:

size(documents.get("article", "welcome-post").tags)

Ergebnis: 2 (Anzahl der Tags)


Parametrische Routen und meta.params

Parametrische Routen sind der Schlüssel zum Aufbau dynamischer, lokalisierter Seiten. Wenn Sie ein Routenmuster wie /{lang}/landingPage definieren, extrahiert das CMS Parameter aus der URL und stellt sie über meta.params bereit.

Funktionsweise von Routenparametern

Definition des Routenmusters: Routen verwenden die Syntax :paramName oder {paramName}, um dynamische Segmente zu definieren:

MusterBeispiel-URLExtrahierte Parameter
/:lang/landingPage/ko/landingPage{ lang: "ko" }
/{country}/{lang}/products/us/en/products{ country: "us", lang: "en" }
/articles/:slug/articles/welcome-post{ slug: "welcome-post" }

Parameter-Bindings: Jeder Routenparameter kann zur Validierung an ein Dokumentenschema gebunden werden:

{
  "pattern": "/{lang}/landingPage",
  "param_bindings": {
    "lang": "language"
  }
}

Diese Bindung teilt dem CMS mit:

  1. Segment lang aus der URL extrahieren
  2. Gegen das language-Schema validieren (sucht nach einem Dokument, bei dem content.code übereinstimmt)
  3. Bei erfolgreicher Validierung das vollständige Dokument in den aufgelösten Parametern bereitstellen

Beispiel: Sprachbasierte Landingpage

Routenkonfiguration:

  • Pfad: /{lang}/landingPage
  • Muster: /{lang}/landingPage
  • Parameter-Bindings: { "lang": "language" }

Ihre Begrüßungsdokumente:

// greeting / ko
{ "code": "ko", "headline": "Willkommen", "subheadline": "Willkommen auf unserer Plattform", "ctaText": "Loslegen", "ctaUrl": "/ko/get-started" }

// greeting / en
{ "code": "en", "headline": "Willkommen", "subheadline"

CEL-Skript zum Abrufen lokalisierter Inhalte:

documents.get("greeting", meta.params.lang).headline

So wird aufgelöst:

URLmeta.params.langErgebnis
/ko/landingPage"ko""Willkommen"
/en/landingPage"en""Willkommen"
/ja/landingPage"ja""Willkommen"

Fortgeschrittenes Muster: Routen mit Land + Sprache

Für Routen wie /{country}/{lang}/products:

Routenkonfiguration:

{
  "pattern": "/{country}/{lang}/products",
  "param_bindings": {
    "country": "country",
    "lang": "language"
  }
}

CEL-Skripte:

// Ländernamen abrufen
documents.get("country", meta.params.country).name

// Lokalisierte Produktliste basierend auf dem Land abrufen
documents.find("product", { "where": { "country": meta.params.country } })

// Kombiniert: Länderspezifische Begrüßung in der Sprache der Nutzerin oder des Nutzers
documents.get("greeting", meta.params.lang).headline + " aus " + documents.get("country", meta.params.country).name

Validierungskaskade: Das CMS validiert Parameter hierarchisch. Für Routen /{country}/{lang} gilt:

  1. country-Parameter gegen das country-Schema validieren
  2. lang-Parameter gegen das language-Schema validieren
  3. Optional prüfen, ob lang im Array country.languages[] enthalten ist (hierarchische Validierung)

meta.segments – Zugriff auf den rohen URL-Pfad

meta.segments liefert den rohen URL-Pfad als Array. Das ist nützlich, wenn Sie positionalen Zugriff ohne benannte Parameter benötigen.

So funktioniert es:

URL-Pfadmeta.segments
/articles/tech/ai-news["articles", "tech", "ai-news"]
/ko/landingPage["ko", "landingPage"]
/us/en/products/featured["us", "en", "products", "featured"]
/[]

Wann meta.segments vs. meta.params verwenden?

AnwendungsfallBeste Wahl
Benannte Parameter aus dem Routenmustermeta.params.lang
Positionsbasierter Zugriffmeta.segments[0]
Pfadtiefe ermittelnsize(meta.segments)
Prüfen, ob Pfad Segment enthält"admin" in meta.segments

Beispiele mit meta.segments

// Erstes Segment holen (oft Sprachcode)
meta.segments[0]

// Pfadtiefe prüfen
size(meta.segments) > 2 ? "tief" : "flach"

// Prüfen, ob wir im Admin-Bereich sind
"admin" in meta.segments ? "Admin-Modus" : "Öffentlicher Modus"

// Fallback: Segment verwenden, wenn Parameter nicht gebunden ist
has(meta.params.lang) ? meta.params.lang : meta.segments[0]

Vollständige Referenz zum Objekt meta

Das Objekt meta enthält den gesamten Kontext zur aktuellen Anfrage:

EigenschaftTypBeschreibung
meta.localestringAktueller Locale-Code (z. B. "en-US", "ko-KR", "ar-SA")
meta.paramsRecord<string, string>Aus dem URL-Muster extrahierte Routenparameter
meta.segmentsstring[]In Segmente aufgeteilter URL-Pfad
meta.docId`string \null`UUID des aktuellen Dokuments (null bei neuen Dokumenten)
meta.titlestringTitel des aktuellen Dokuments

meta.locale

Der Locale-Code folgt dem BCP-47-Format (Sprache-Region):

// Locale auf RTL-Sprachen prüfen
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"

// Nur den Sprachteil holen
meta.locale.split("-")[0]  // Nicht unterstützt – verwenden Sie stattdessen meta.params.lang

meta.params

Routenparameter sind immer Strings. Das CMS validiert sie vor der Auswertung gegen die gebundenen Schemata:

// Benannten Parameter abrufen
meta.params.lang           // "ko"
meta.params.country        // "us"
meta.params.slug           // "welcome-post"

// Prüfen, ob Parameter existiert
has(meta.params.category)  // true/false

// In Dokumentabruf verwenden
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)

meta.segments

Rohe URL-Segmente als Array:

// Zugriff per Index (0-basiert)
meta.segments[0]           // Erstes Segment
meta.segments[1]           // Zweites Segment

// Länge prüfen
size(meta.segments)        // Anzahl der Segmente

// Mitgliedschaft prüfen
"products" in meta.segments  // Enthält der Pfad "products"?

meta.docId

Die UUID des aktuellen Dokuments, nützlich für selbstreferenzierende Skripte:

// Nur verfügbar beim Bearbeiten bestehender Dokumente
meta.docId != null ? "Bearbeitung" : "Neues Dokument"

// In Bedingungslogik verwenden
meta.docId != null ? documents.get("article", meta.docId).status : "draft"

meta.title

Der Titel des aktuellen Dokuments:

// Zur Anzeige nutzen
"Bearbeite: " + meta.title

// Bedingung basierend auf dem Titel
meta.title.contains("Draft") ? "In Arbeit" : "Veröffentlicht"

documents.ref() – Verkettete Abrufe

Für eine übersichtlichere Syntax, wenn das Schema bekannt ist, der Bezeichner aber dynamisch:

// Traditioneller Ansatz
documents.get("airports", meta.params.code).name

// Mit ref() – Schema getrennt vom dynamischen Bezeichner
documents.ref("airports").get(meta.params.code).name

Beides ist äquivalent, aber ref() macht den dynamischen Teil klarer.


Kurzübersicht

Dokumentabruf

documents.get("schema", "identifier")       // Ein Dokument abrufen
documents.get("schema", "id").fieldName     // Spezifisches Feld abrufen
documents.find("schema")                    // Alle Dokumente abrufen
documents.find("schema", { "where": {...}}) // Gefilterte Abfrage
documents.ref("schema").get(identifier)     // Verketteter Abruf
documents.translated("schema", "id", "fr")  // Mit expliziter Locale abrufen

Kontextvariablen

meta.locale          // "en-US", "ar-SA" usw.
meta.params.xyz      // URL-Parameter mit Namen "xyz"
meta.segments        // URL-Pfad als Array: ["articles", "intro"]
meta.segments[0]     // Erstes Pfadsegment
meta.docId           // Aktuelle Dokument-ID (oder null)
meta.title           // Titel des aktuellen Dokuments
doc.fieldName        // Feldwert des aktuellen Dokuments (im Editor-Kontext)

Operatoren

// Vergleich
==  !=  <  <=  >  >=

// Logik
&&  ||  !

// Ternär (if-else)
Bedingung ? WertWennWahr : WertWennFalsch

// Mitgliedschaft
"Wert" in listeOderMap

Häufige Funktionen

size(list)                    // Anzahl der Elemente
size(string)                  // Stringlänge
"text".startsWith("te")       // true
"text".endsWith("xt")         // true
"text".contains("ex")         // true
has(object.property)          // Prüfen, ob Eigenschaft existiert
hasProperty(obj, "key")       // Prüfen, ob Objekt Schlüssel hat (alternative Syntax)

Fehlermeldungen

Wenn etwas schiefgeht, sehen Sie eine dieser Meldungen:

FehlerBedeutung
SYNTAX_ERRORTippfehler im Skript (fehlendes Anführungszeichen, falscher Operator)
TYPE_ERRORSie mischen inkompatible Typen
RUNTIME_ERRORDas Skript lief, stieß aber auf ein Problem (undefinierte Variable)
FETCH_LIMIT_EXCEEDEDEs werden zu viele Dokumente abgerufen (maximal 50)
TIMEOUTSkript dauerte zu lange (maximal 5 Sekunden)
AST_DEPTH_EXCEEDEDAusdruck zu tief verschachtelt (maximale Tiefe: 50)
SCRIPT_TOO_LONGSkript überschreitet das Limit von 5000 Zeichen

Erweiterbarkeit & zukünftige Funktionen

Die CEL-Engine ist auf Erweiterbarkeit ausgelegt. Geplante zukünftige Funktionen umfassen:

Geplant: MCP-Server-Integration

// Zukunft: Externe Dienste über MCP aufrufen
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)

Geplant: KI-Funktionen

// Zukunft: KI-gestützte Inhaltserzeugung
ai.summarize(documents.get("article", meta.params.id).body, 100)
ai.translate(meta.params.text, meta.params.targetLang)
ai.classify(meta.params.input, ["positive", "negative", "neutral"])

Diese Funktionen werden über das registrierte Funktionssystem hinzugefügt und bleiben rückwärtskompatibel mit bestehenden Skripten.


Tipps

  1. Autocomplete nutzen – Tippen Sie documents. oder meta., und der Editor zeigt verfügbare Optionen an
  2. Einfach beginnen – Testen Sie zuerst mit documents.get("schema", "id") und fügen Sie dann .fieldName hinzu
  3. Auf null prüfen – Wenn ein Dokument fehlen könnte, bauen Sie einen Fallback mit != null ? ... : ... ein
  4. Nicht zu viel abrufen – Jeder Aufruf documents.get() oder documents.find() zählt gegen das 50-Abruf-Limit
  5. meta.params gegenüber meta.segments bevorzugen – Benannte Parameter sind validiert und zuverlässiger
  6. has() für optionale Parameter verwenden – Prüfen Sie has(meta.params.category), bevor Sie darauf zugreifen
  7. documents.ref() für dynamische Bezeichner nutzen – Klarere Syntax, wenn das Schema fest, der Bezeichner aber dynamisch ist
  8. doc.fieldName für Selbstreferenzen verwenden – Greifen Sie innerhalb berechneter Ausdrücke auf Felder des aktuellen Dokuments zu

Dokument-zu-Dokument-Verweise

Dieser Abschnitt behandelt fortgeschrittene Muster, um Dokumente miteinander zu verknüpfen und relationale Inhaltsstrukturen aufzubauen.

Einfaches Referenzmuster

Der simpelste Fall: Ein Dokument verweist über einen Bezeichner auf ein anderes.

// Artikel speichert Autor-ID, Name der Autorin abrufen
documents.get("author", documents.get("article", "intro").authorId).name

Verkettete Abrufe mit documents.ref()

Für eine klarere Syntax, wenn der Bezeichner dynamisch ist:

// Traditioneller Ansatz
documents.get("country", documents.get("airport", meta.params.code).countryCode).name

// Mit ref() – klarer, wenn Schema bekannt, Bezeichner dynamisch
documents.ref("country").get(documents.get("airport", meta.params.code).countryCode).name

Mehrstufige Referenzketten

Bauen Sie tiefe Beziehungen auf, indem Sie mehrere Abrufe verketten:

// Flughafen → Land → Region → Kontinent
documents.get("continent",
  documents.get("region",
    documents.get("country",
      documents.get("airport", meta.params.code).countryCode
    ).regionCode
  ).continentCode
).name

Referenz mit Übersetzung

Dokumentenverweise mit Übersetzungen kombinieren:

// Lokalisierter Ländername für einen Flughafen
documents.translated("country",
  documents.get("airport", meta.params.code).countryCode,
  meta.params.lang
).name

Referenzmuster nach Anwendungsfall

Muster 1: Lookup über Fremdschlüssel

Ein Dokument speichert eine ID, die auf ein anderes Dokument verweist.

// article / tech-news
{ "title": "Technik-Update", "authorId": "author-123", "categoryId": "cat-tech" }
// Namen der Autorin auflösen
documents.get("author", documents.get("article", meta.params.slug).authorId).name

// Kategorie mit Fallback auflösen
documents.get("article", meta.params.slug).categoryId != null
  ? documents.get("category", documents.get("article", meta.params.slug).categoryId).name
  : "Keine Kategorie"

Muster 2: Codebasierte Referenzen

Dokumente verweisen über semantische Codes anstelle von UUIDs aufeinander.

// airport / JFK
{ "code": "JFK", "name": "John F. Kennedy International", "countryCode": "us" }

// country / us
{ "code": "us", "name": "Vereinigte Staaten", "currencyCode": "usd" }

// currency / usd
{ "code": "usd", "symbol"
// Flughafen → Land → Währung
documents.get("currency",
  documents.get("country",
    documents.get("airport", meta.params.code).countryCode
  ).currencyCode
).symbol
// Für JFK: Gibt "$" zurück

Muster 3: Selbstreferenz mit doc-Kontext

Verwenden Sie doc für berechnete Felder, die andere Dokumente anhand der Werte des aktuellen Dokuments referenzieren.

// In einem Produktdokument die zugehörige Kategoriebeschreibung abrufen
documents.get("category", doc.categoryId).description

// Versandkosten basierend auf dem Herkunftsland des Produkts berechnen
documents.get("shipping-rates", doc.originCountry).baseRate * doc.weight

Muster 4: Bidirektionale Referenzen

Wenn Dokumente sich gegenseitig referenzieren, achten Sie auf Abruflimits.

// Autorin eines Artikels abrufen und dann deren andere Artikel (Abrufzahl im Blick behalten!)
documents.find("article", { "where": { "authorId": documents.get("article", meta.params.slug).authorId } })

Muster 5: Polymorphe Referenzen

Wenn ein Feld auf unterschiedliche Schemata verweisen kann:

// content-block / hero-1
{ "type": "hero", "sourceType": "article", "sourceId": "welcome-post" }

// content-block / hero-2
{ "type": "hero", "sourceType": "product", "sourceId": "featured-item" }
// Dynamisches Schema basierend auf sourceType
documents.get("content-block", "hero-1").sourceType == "article"
  ? documents.get("article", documents.get("content-block", "hero-1").sourceId).headline
  : documents.get("product", documents.get("content-block", "hero-1").sourceId).name

Abhängigkeitsverfolgung

Jeder Aufruf von documents.get(), documents.find() und documents.ref().get() wird für die Cache-Invalidierung nachverfolgt. Sobald sich ein referenziertes Dokument ändert, weiß das CMS, welche CEL-Ausdrücke neu ausgewertet werden müssen.

Verfolgte Abhängigkeiten umfassen:

  • get: schema:identifier – Abhängigkeit von einem konkreten Dokument
  • ref: schema:identifier – Wie get, aber über die verkettete Syntax
  • query: schema:* – Schemaweite Abhängigkeit (jedes Dokument im Schema)

Best Practices für Referenzen

  1. Verkettungstiefe minimieren – Jede Ebene erhöht Latenz und Abrufzahl
  2. Zwischenergebnisse cachen – Wenn Sie denselben verschachtelten Wert zweimal benötigen, holen Sie das übergeordnete Dokument nur einmal
  3. Null-Prüfungen verwenden – Referenzen können brechen, wenn Dokumente gelöscht werden
  4. Codes statt UUIDs bevorzugen – Codes sind lesbar und in verschiedenen Umgebungen stabil
  5. Abruflimits beachten – Komplexe Referenzketten können schnell das 50-Abruf-Limit erreichen
// Schlecht: Holt dasselbe Dokument zweimal
documents.get("author", documents.get("article", "intro").authorId).name + " - " +
documents.get("author", documents.get("article", "intro").authorId).bio

// Besser: Einmal prüfen und verwenden
documents.get("article", "intro").authorId != null
  ? documents.get("author", documents.get("article", "intro").authorId).name
  : "Unbekannte Autorin"

Anhang A: Vollständiges Beispiel für eine parametrische Route

Dieser Leitfaden erstellt eine mehrsprachige Landingpage, erreichbar unter /{lang}/landingPage.

Schritt 1: Begrüßungs-Schema erstellen

Erstellen Sie im CMS-Admin ein benutzerdefiniertes Schema namens greeting:

{
  "name": "greeting",
  "fields": [
    { "name": "code", "type": "string", "required": true },
    { "name": "headline", "type": "string", "required": true },
    { "name": "subheadline"

Schritt 2: Begrüßungsdokumente anlegen

Erstellen Sie Dokumente für jede Sprache:

Dokument: greeting / ko

{
  "code": "ko",
  "headline": "Willkommen",
  "subheadline": "Willkommen auf unserer Plattform",
  "ctaText": "Loslegen",
  "ctaUrl": "/ko/get-started"
}

Dokument: greeting / en

{
  "code": "en",
  "headline": "Willkommen",
  "subheadline": "Willkommen auf unserer Plattform",
  "ctaText": "Jetzt starten",
  "ctaUrl": "/en/get-started"
}

Dokument: greeting / ja

{
  "code": "ja",
  "headline": "Willkommen",
  "subheadline": "Willkommen auf unserer Plattform",
  "ctaText": "Start",
  "ctaUrl": "/ja/get-started"
}

Schritt 3: Route erstellen

Legen Sie eine Route mit folgender Konfiguration an:

  • Pfad: /{lang}/landingPage
  • Muster: /{lang}/landingPage
  • Status: Live
  • Parameter-Bindings:
  {
    "lang": "language"
  }

Schritt 4: Blöcke mit CEL-Skripten hinzufügen

Fügen Sie der Route einen Hero-Block hinzu und verwenden Sie diese CEL-Skripte für die einzelnen Felder:

Überschriftenfeld:

documents.get("greeting", meta.params.lang).headline

Unterüberschriftenfeld:

documents.get("greeting", meta.params.lang).subheadline

CTA-Text-Feld:

documents.get("greeting", meta.params.lang).ctaText

CTA-URL-Feld:

documents.get("greeting", meta.params.lang).ctaUrl

Schritt 5: In Next.js verwenden

Erstellen Sie eine Catch-all-Route in Ihrer Next.js-App:

// app/[...slug]/page.tsx
import { getCmsClient } from '@repo/renderer';

interface PageProps {
  params: { slug: string[] };
}

export default async function Page({ params }: PageProps) {

Schritt 6: Routen testen

Besuchen Sie diese URLs, um lokalisierte Inhalte anzuzeigen:

URLErwartete Überschrift
/ko/landingPageWelcome
/en/landingPageWelcome
/ja/landingPageWelcome

So erfolgt die Auflösung

Wenn jemand /ko/landingPage besucht:

  1. Routenabgleich: CMS matcht das Muster /{lang}/landingPage
  2. Parameterextraktion: meta.params.lang = "ko"
  3. Validierung: CMS validiert „ko“ gegen das language-Schema
  4. CEL-Auswertung: Skripte wie documents.get("greeting", meta.params.lang) liefern koreanische Inhalte
  5. Antwort: Lokalisierte Blöcke werden an den Client zurückgegeben

Anhang B: Technische Referenz

CelMeta-Schnittstelle (TypeScript)

interface CelMeta {
  /** Aktueller Locale-Code (z. B. 'en-US') */
  locale: string;
  /** Aus der URL extrahierte Routenparameter */
  params: Record<string, string>;
  /** URL-Pfadsegmente */
  segments: string[];
  /** ID des aktuellen Dokuments (bei Bearbeitung) */

Algorithmus zur Parameterextraktion

Die Funktion extractParams verarbeitet URL-Pfade:

Muster:  /{country}/{lang}/products
Pfad:    /us/en/products

Algorithmus:
1. Beide normalisieren (abschließende Slashes entfernen)
2. In Segmente teilen: ["us", "en", "products"] und ["{country}", "{lang}", "products"

Unterstützte Formate für Parameter-Bindings

// Einfache Bindung (nutzt das Feld "code" zur Suche)
{ "lang": "language" }

// Detaillierte Bindung (benutzerdefiniertes Slug-Feld)
{
  "lang": {
    "schemaName": "language",
    "slugField": "code"
  },
  "slug": {
    "schemaName": "article",
    "slugField": "slug"
  }

Priorität bei Dokumentabrufen

Beim Abruf über documents.get(schema, identifier) gilt:

  1. UUID-Treffer: Wenn der Bezeichner eine gültige UUID ist, erfolgt der Abruf über id
  2. Code-Feld: Abgleich mit content.code
  3. Slug-Feld: Abgleich mit content.slug
  4. Titelabgleich: Abgleich mit dem Feld title

So können Dokumente flexibel über jeden eindeutigen Identifikator referenziert werden.

]
] }
:
"Willkommen auf unserer Plattform"
,
"ctaText"
:
"Jetzt starten"
,
"ctaUrl"
:
"/en/get-started"
}
// greeting / ja
{ "code": "ja", "headline": "Willkommen", "subheadline": "Willkommen auf unserer Plattform", "ctaText": "Start", "ctaUrl": "/ja/get-started" }
:
"$"
,
"name"
:
"US-Dollar"
}
,
"type"
:
"string"
},
{ "name": "ctaText", "type": "string" },
{ "name": "ctaUrl", "type": "string" }
]
}
const
client
=
getCmsClient
({
cmsUrl: process.env.CMS_URL!,
apiKey: process.env.CMS_API_KEY!,
websiteId: process.env.CMS_WEBSITE_ID!,
});
const path = '/' + params.slug.join('/');
// Route und aufgelöste Parameter abrufen
const { route, resolvedParams } = await client.routes.getRouteByPath.query({
websiteId: process.env.CMS_WEBSITE_ID!,
path,
});
// Blöcke für die Route abrufen
const blocks = await client.blocks.getBlocks.query({
websiteId: process.env.CMS_WEBSITE_ID!,
blockIds: route.block_ids,
// Aufgelöste Parameter für den CEL-Kontext übergeben
context: {
meta: {
locale: resolvedParams?.lang?.document?.content?.code ?? 'en',
params: Object.fromEntries(
Object.entries(resolvedParams ?? {}).map(([k, v]) => [k, v.value])
),
segments: params.slug,
docId: null,
title: route.title ?? '',
},
schema: {},
},
});
// Blöcke rendern
return (
<main>
{blocks.map((block) => (
<BlockRenderer
key={block.id}
block={block}
routeParams={resolvedParams}
language={resolvedParams?.lang?.value}
/>
))}
</main>
);
}
docId
:
string
|
null
;
/** Titel des aktuellen Dokuments */
title: string;
}
]
3. Segmentanzahl abgleichen (muss identisch sein)
4. Für jedes Segmentpaar:
- Wenn Muster mit : oder {} beginnt, als Parameter extrahieren
- Andernfalls muss es exakt übereinstimmen
5. Rückgabe: { country: "us", lang: "en" }
}