Een praktische gids voor het schrijven van CEL-expressies in het CMS.
Een praktische gids voor het schrijven van CEL-expressies in het CMS.
CEL (Common Expression Language) is een lichtgewicht scripttaal die in ons CMS is ingebouwd. Hiermee kun je dynamische expressies schrijven die gegevens uit documenten ophalen, URL-parameters lezen en waarden direct berekenen.
Dit gebeurt er wanneer een CEL-script wordt uitgevoerd:
Jouw script De engine Resultaat
| | |
v v v
documents.get("article", "intro") --> Haalt op uit database --> { headline: "Welkom", body: "..." }
.headline --> Haalt het veld op --> "Welkom"
Zie CEL als een alleen-lezen querytaal. Het kan niets in de database wijzigen - het leest alleen gegevens en geeft een berekend resultaat terug. Hierdoor is het overal in het CMS veilig te gebruiken.
Elke CEL-expressie heeft toegang tot drie zaken:
| Object | Wat het is | Voorbeeld |
|---|---|---|
documents | Haal elk document op uit het CMS | documents.get("country", "us") |
meta | Informatie over de huidige aanvraag (locale, URL-parameters) | meta.locale, meta.params.slug |
schema | De velddefinities van het huidige document | schema.fields |
De krachtigste functie van CEL is het ophalen van documenten vanuit elke plek in je CMS.
Syntaxis: documents.get(schemaName, identifier)
Stel, je hebt een article-document opgeslagen met de identificatie "welcome-post":
// Opgeslagen in het CMS als: article / welcome-post
{
"headline": "Welkom op ons platform",
"author": "Sarah Chen",
"body": "We zijn verheugd om aan te kondigen...",
"tags": ["aankondiging", "nieuws"]
}
Om het volledige document op te halen:
documents.get("article", "welcome-post")
Geeft terug:
{
"headline": "Welkom op ons platform",
"author": "Sarah Chen",
"body": "We zijn verheugd om aan te kondigen...",
"tags": ["aankondiging", "nieuws"]
}
Om alleen de kop op te halen:
documents.get("article", "welcome-post").headline
Geeft terug: "Welkom op ons platform"
Om de auteur op te halen:
documents.get("article", "welcome-post").author
Geeft terug: "Sarah Chen"
Wanneer je pagina dynamische routes heeft (zoals /articles/[slug]), kun je meta.params gebruiken om de URL-parameter op te halen en het juiste document binnen te halen.
Als iemand /articles/welcome-post bezoekt:
documents.get("article", meta.params.slug).headline
Geeft terug: "Welkom op ons platform"
Zo bouw je dynamische pagina's - hetzelfde CEL-script werkt voor elk artikel, met welke slug er ook in de URL staat.
Syntaxis: documents.find(schemaName) of documents.find(schemaName, filter)
// Haal alle landen op
documents.find("country")
Geeft terug:
[
{ "code": "us", "name": "Verenigde Staten", "flag": "US" },
{ "code": "sa", "name": "Saoedi-Arabië", "flag": "SA" },
{ "code": "gb", "name": "Verenigd Koninkrijk", "flag": "GB" }
// Haal landen op met een filter
documents.find("country", { "where": { "code": "us" } })
Geeft terug:
[
{ "code": "us", "name": "Verenigde Staten", "flag": "US" }
]
Je hebt een hero-block dat een kop moet tonen die uit een article-document wordt gehaald.
Jouw artikeldocument (identificatie: "homepage-hero"):
{
"headline": "Bouw sneller, lanceer slimmer",
"subheadline": "Het moderne CMS voor ontwikkelaars"
}
CEL-script in het titelveld van het hero-blok:
documents.get("article", "homepage-hero").headline
Resultaat: De hero toont "Bouw sneller, lanceer slimmer"
Je bouwt een pagina op /countries/[code] en wilt de volledige landnaam tonen.
Jouw landendocumenten:
// country / us
{ "code": "us", "name": "Verenigde Staten", "flag": "US", "languages": ["en", "es"] }
// country / sa
{ "code": "sa", "name": "Saoedi-Arabië", "flag": "SA", "languages": ["ar", "en"
CEL-script:
documents.get("country", meta.params.code).name
Wanneer iemand /countries/us bezoekt:
meta.params.code = "us""Verenigde Staten"Wanneer iemand /countries/sa bezoekt:
meta.params.code = "sa""Saoedi-Arabië"Toon verschillende koppen op basis van de locale van de gebruiker.
meta.locale == "ar-SA" ? "مرحبا بكم" : "Welkom"
Als de locale "ar-SA" is: Geeft "مرحبا بكم" terug
Als de locale iets anders is: Geeft "Welkom" terug
Je article heeft een countryCode-veld en je wilt de volledige landnaam ophalen.
Artikeldocument:
{ "headline": "Nieuws uit de VS", "countryCode": "us" }
CEL-script:
documents.get("country", documents.get("article", "us-news").countryCode).name
Wat er gebeurt:
documents.get("article", "us-news") geeft { "headline": "Nieuws uit de VS", "countryCode": "us" } terug.countryCode haalt "us" opdocuments.get("country", "us") geeft { "code": "us", "name": "Verenigde Staten", ... } terug.name haalt "Verenigde Staten" opResultaat: "Verenigde Staten"
Als een document mogelijk niet bestaat, kun je een fallback opgeven:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Artikel niet gevonden"
Of controleer of een specifiek veld bestaat:
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "Onbekende auteur"
Je artikel heeft tags en je wilt controleren of een specifieke tag bestaat:
"uitgelicht" in documents.get("article", "welcome-post").tags
Geeft terug: true als het artikel de tag "uitgelicht" heeft
Haal de eerste tag op:
documents.get("article", "welcome-post").tags[0]
Geeft terug: "aankondiging" (de eerste tag)
Tel de tags:
size(documents.get("article", "welcome-post").tags)
Geeft terug: 2 (aantal tags)
Parametrische routes zijn de sleutel tot het bouwen van dynamische, gelokaliseerde pagina's. Wanneer je een routepatroon definieert zoals /{lang}/landingPage, extraheert het CMS parameters uit de URL en stelt het ze beschikbaar via meta.params.
Definitie van routepatroon:
Routes gebruiken :paramName of {paramName}-syntaxis om dynamische segmenten te definiëren:
| Patroon | Voorbeeld-URL | Uitgelezen parameters |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
Parameterbindingen: Elk routeparameter kan aan een documentschema worden gebonden voor validatie:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
Deze binding vertelt het CMS:
lang-segment uit de URLlanguage-schema (zoekt naar een document waarbij content.code overeenkomt)Routeconfiguratie:
/{lang}/landingPage/{lang}/landingPage{ "lang": "language" }Jouw begroetingsdocumenten:
// greeting / ko
{ "code": "ko", "headline": "환영합니다", "subheadline": "우리 플랫폼에 오신 것을 환영합니다" }
// greeting / en
{ "code": "en", "headline": "Welcome", "subheadline": "Welcome to our platform" }
// greeting / ja
{ "code": "ja", "headline"
CEL-script om gelokaliseerde inhoud op te halen:
documents.get("greeting", meta.params.lang).headline
Hoe het wordt opgelost:
| URL | meta.params.lang | Resultaat |
|---|---|---|
/ko/landingPage | "ko" | "환영합니다" |
/en/landingPage | "en" | "Welcome" |
/ja/landingPage | "ja" | "ようこそ" |
Voor routes zoals /{country}/{lang}/products:
Routeconfiguratie:
{
"pattern": "/{country}/{lang}/products",
"param_bindings": {
"country": "country",
"lang": "language"
}
}
CEL-scripts:
// Haal landnaam op
documents.get("country", meta.params.country).name
// Haal gelokaliseerde productlijst op op basis van land
documents.find("product", { "where": { "country": meta.params.country } })
// Gecombineerd: Toon landspecifieke begroeting in de taal van de gebruiker
documents.get("greeting", meta.params.lang).headline + " uit " + documents.get("country", meta.params.country).name
Validatiecascade:
Het CMS valideert parameters hiërarchisch. Voor /{country}/{lang}-routes:
country-parameter tegen het country-schemalang-parameter tegen het language-schemalang in de array country.languages[] staat (hiërarchische validatie)meta.segments biedt het onbewerkte URL-pad als een array, handig wanneer je positionele toegang nodig hebt zonder benoemde parameters.
Hoe het werkt:
| URL-pad | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
| Gebruikssituatie | Beste aanpak |
|---|---|
| Benoemde parameters uit routepatroon | meta.params.lang |
| Toegang op basis van positie | meta.segments[0] |
| Paddiepte opvragen | size(meta.segments) |
| Controleren of het pad een segment bevat | "admin" in meta.segments |
// Haal het eerste segment op (vaak taalcodes)
meta.segments[0]
// Controleer pad-diepte
size(meta.segments) > 2 ? "diep" : "ondiep"
// Controleer of we in het beheergedeelte zitten
"admin" in meta.segments ? "beheermodus" : "publieke modus"
// Fallback: gebruik segment als parameter niet is gebonden
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
Het meta-object bevat alle context over de huidige aanvraag:
| Eigenschap | Type | Beschrijving | |
|---|---|---|---|
meta.locale | string | Huidige locale-code (bijv. "en-US", "ko-KR", "ar-SA") | |
meta.params | Record<string, string> | Routeparameters die uit het URL-patroon zijn gehaald | |
meta.segments | string[] | URL-pad opgesplitst in segmenten | |
meta.docId | `string \ | null` | Huidige document-UUID (null voor nieuwe documenten) |
meta.title | string | Huidige documenttitel |
De locale-code volgt het BCP 47-formaat (taal-regio):
// Controleer locale voor RTL-talen
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// Haal alleen het taalgedeelte op
meta.locale.split("-")[0] // Niet ondersteund - gebruik in plaats daarvan meta.params.lang
Routeparameters zijn altijd strings. Het CMS valideert ze tegen gebonden schema's vóór evaluatie:
// Toegang tot benoemde parameter
meta.params.lang // "ko"
meta.params.country // "us"
meta.params.slug // "welcome-post"
// Controleer of parameter bestaat
has(meta.params.category) // true/false
// Gebruik bij documentophaal
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
Onbewerkte URL-segmenten als array:
// Toegang op index (0-based)
meta.segments[0] // Eerste segment
meta.segments[1] // Tweede segment
// Controleer lengte
size(meta.segments) // Aantal segmenten
// Controleer lidmaatschap
"products" in meta.segments // Bevat het pad "products"?
De UUID van het huidige document, handig voor zelf-referentiële scripts:
// Alleen beschikbaar bij het bewerken van bestaande documenten
meta.docId != null ? "bezig met bewerken" : "nieuw aanmaken"
// Gebruik in conditionele logica
meta.docId != null ? documents.get("article", meta.docId).status : "concept"
De titel van het huidige document:
// Gebruik voor weergave
"Bewerken: " + meta.title
// Conditioneel op basis van titel
meta.title.contains("Concept") ? "werk in uitvoering" : "gepubliceerd"
Voor een overzichtelijkere syntaxis wanneer het schema bekend is maar de identificatie dynamisch is:
// Traditionele aanpak
documents.get("airports", meta.params.code).name
// ref() gebruiken - schema gescheiden van dynamische identificatie
documents.ref("airports").get(meta.params.code).name
Beide zijn gelijkwaardig, maar ref() maakt het dynamische gedeelte duidelijker.
documents.get("schema", "identifier") // Haal één document op
documents.get("schema", "id").fieldName // Haal een specifiek veld op
documents.find("schema") // Haal alle documenten op
documents.find("schema", { "where": {...}}) // Gefilterde query
documents.ref("schema").get(identifier) // Gekoppelde opzoeking
meta.locale // "en-US", "ar-SA", enz.
meta.params.xyz // URL-parameter met de naam "xyz"
meta.segments // URL-pad als array: ["articles", "intro"]
meta.segments[0] // Eerste padsegment
meta.docId // Huidige document-ID (of null)
meta.title // Huidige documenttitel
// Vergelijking
== != < <= > >=
// Logica
&& || !
// Ternair (if-else)
voorwaarde ? waardeAlsWaar : waardeAlsOnwaar
// Lidmaatschap
"waarde" in lijstOfMap
size(list) // Tel items
size(string) // Lengte van string
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // Controleer of eigenschap bestaat
Als er iets misgaat, zie je een van deze meldingen:
| Fout | Betekenis |
|---|---|
SYNTAX_ERROR | Typfout in je script (ontbrekend aanhalingsteken, verkeerde operator) |
TYPE_ERROR | Je combineert typen die niet samenwerken |
RUNTIME_ERROR | Het script draaide maar liep tegen een probleem aan (onbepaalde variabele) |
FETCH_LIMIT_EXCEEDED | Je haalt te veel documenten op (max. 50) |
TIMEOUT | Script duurde te lang (max. 5 seconden) |
AST_DEPTH_EXCEEDED | Expressie te diep genest (maximale diepte: 50) |
SCRIPT_TOO_LONG | Script overschrijdt de limiet van 5000 tekens |
De CEL-engine is ontworpen voor uitbreidbaarheid. Toekomstige mogelijkheden omvatten:
// Toekomst: externe services aanroepen via MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// Toekomst: door AI aangestuurde contentgeneratie
ai.summarize(documents.get("article", meta.params.id).body, 100)
ai.translate(meta.params.text, meta.params.targetLang)
ai.classify(meta.params.input, ["positief", "negatief", "neutraal"])
Deze mogelijkheden worden toegevoegd via het systeem voor geregistreerde functies en behouden achterwaartse compatibiliteit met bestaande scripts.
documents. of meta. en de editor toont beschikbare optiesdocuments.get("schema", "id") en voeg daarna .fieldName toe!= null ? ... : ...documents.get() of documents.find() telt mee voor de limiet van 50 ophalenhas(meta.params.category) voordat je deze benadertDeze walkthrough maakt een meertalige landingspagina die toegankelijk is via /{lang}/landingPage.
Maak in de CMS-beheeromgeving een aangepast schema genaamd greeting:
{
"name": "greeting",
"fields": [
{ "name": "code", "type": "string", "required": true },
{ "name": "headline", "type": "string", "required": true },
{ "name": "subheadline"
Maak documenten voor elke taal:
Document: greeting / ko
{
"code": "ko",
"headline": "환영합니다",
"subheadline": "우리 플랫폼에 오신 것을 환영합니다",
"ctaText": "시작하기",
"ctaUrl": "/ko/get-started"
}
Document: greeting / en
{
"code": "en",
"headline": "Welcome",
"subheadline": "Welcome to our platform",
"ctaText": "Aan de slag",
"ctaUrl": "/en/get-started"
}
Document: greeting / ja
{
"code": "ja",
"headline": "ようこそ",
"subheadline": "私たちのプラットフォームへようこそ",
"ctaText": "始める",
"ctaUrl": "/ja/get-started"
}
Maak een route met de volgende configuratie:
/{lang}/landingPage/{lang}/landingPage {
"lang": "language"
}
Voeg een hero-blok toe aan de route met deze CEL-scripts voor elk veld:
Kopveld:
documents.get("greeting", meta.params.lang).headline
Subkopveld:
documents.get("greeting", meta.params.lang).subheadline
CTA-tekstveld:
documents.get("greeting", meta.params.lang).ctaText
CTA-URL-veld:
documents.get("greeting", meta.params.lang).ctaUrl
Maak een catch-all route in je Next.js-app:
// app/[...slug]/page.tsx
import { getCmsClient } from '@repo/renderer';
interface PageProps {
params: { slug: string[] };
}
export default async function Page({ params }: PageProps) {
Bezoek deze URL's om gelokaliseerde inhoud te zien:
| URL | Verwachte kop |
|---|---|
/ko/landingPage | 환영합니다 |
/en/landingPage | Welcome |
/ja/landingPage | ようこそ |
Wanneer een gebruiker /ko/landingPage bezoekt:
/{lang}/landingPagemeta.params.lang = "ko"language-schemadocuments.get("greeting", meta.params.lang) leveren Koreaanse inhoud opinterface CelMeta {
/** Huidige locale-code (bijv. 'en-US') */
locale: string;
/** Routeparameters die uit de URL zijn gehaald */
params: Record<string, string>;
/** URL-padsegmenten */
segments: string[];
/** Huidige document-ID (bij bewerken bestaand document) */
De functie extractParams verwerkt URL-paden:
Patroon: /{country}/{lang}/products
Pad: /us/en/products
Algoritme:
1. Normaliseer beide (verwijder afsluitende slashes)
2. Splits in segmenten: ["us", "en", "products"] en ["{country}", "{lang}"
// Eenvoudige binding (gebruikt "code"-veld voor lookup)
{ "lang": "language" }
// Gedetailleerde binding (aangepast slug-veld)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
Bij ophalen via documents.get(schema, identifier):
idcontent.codecontent.slugtitleDit biedt flexibele documentverwijzingen via elke unieke identificatie.