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

Scripting In Sjabloonbouwer

Een praktische gids voor het schrijven van CEL-expressies in het CMS.

Continue Reading
Previous‹Statische Rendering Met Ondersteuning Voor BewerkmodusNextCreate Profound Next›

Hybride

Renderer ProjectParametrische RouteringComponenttypenSseInstellen Beheerderspaneel ProxyStatische Rendering Met Ondersteuning Voor BewerkmodusScripting In SjabloonbouwerCreate Profound Next

Koploos

SnelstartJson En Claude CodeComponent Zod Ophalen

Mcp

Mcp

Cms Functies

Feat Docs TemplateFeat SjabloonbouwerFeat VertalerFeat Organisatie

Motivatie

Onze Aanpak

Terminologie

Hybride Vs Headless

Een praktische gids voor het schrijven van CEL-expressies in het CMS.


Hoe CEL werkt

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.


De bouwstenen

Elke CEL-expressie heeft toegang tot drie zaken:

ObjectWat het isVoorbeeld
documentsHaal elk document op uit het CMSdocuments.get("country", "us")
metaInformatie over de huidige aanvraag (locale, URL-parameters)meta.locale, meta.params.slug
schemaDe velddefinities van het huidige documentschema.fields

Documenten ophalen

De krachtigste functie van CEL is het ophalen van documenten vanuit elke plek in je CMS.

Eén document ophalen

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"


URL-parameters gebruiken

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.


Meerdere documenten ophalen

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" }
]

Voorbeelden uit de praktijk

Voorbeeld 1: Hero-bloktitel uit een ander document

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"


Voorbeeld 2: Landnaam op basis van code

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"
  • Resultaat: "Verenigde Staten"

Wanneer iemand /countries/sa bezoekt:

  • meta.params.code = "sa"
  • Resultaat: "Saoedi-Arabië"

Voorbeeld 3: Conditionele inhoud op basis van locale

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


Voorbeeld 4: Gekoppelde documentopzoekingen

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:

  1. documents.get("article", "us-news") geeft { "headline": "Nieuws uit de VS", "countryCode": "us" } terug
  2. .countryCode haalt "us" op
  3. documents.get("country", "us") geeft { "code": "us", "name": "Verenigde Staten", ... } terug
  4. .name haalt "Verenigde Staten" op

Resultaat: "Verenigde Staten"


Voorbeeld 5: Fallback-waarden

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"

Voorbeeld 6: Werken met lijsten

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 en meta.params

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.

Hoe routeparameters werken

Definitie van routepatroon: Routes gebruiken :paramName of {paramName}-syntaxis om dynamische segmenten te definiëren:

PatroonVoorbeeld-URLUitgelezen 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:

  1. Haal het lang-segment uit de URL
  2. Valideer het tegen het language-schema (zoekt naar een document waarbij content.code overeenkomt)
  3. Maak bij geldigheid het volledige document beschikbaar in opgeloste parameters

Voorbeeld: Taalgebaseerde landingspagina

Routeconfiguratie:

  • Pad: /{lang}/landingPage
  • Patroon: /{lang}/landingPage
  • Parameterbindingen: { "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:

URLmeta.params.langResultaat
/ko/landingPage"ko""환영합니다"
/en/landingPage"en""Welcome"
/ja/landingPage"ja""ようこそ"

Geavanceerd patroon: land + taal-routes

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:

  1. Valideert country-parameter tegen het country-schema
  2. Valideert lang-parameter tegen het language-schema
  3. Valideert optioneel dat lang in de array country.languages[] staat (hiërarchische validatie)

meta.segments - Onbewerkte URL-padtoegang

meta.segments biedt het onbewerkte URL-pad als een array, handig wanneer je positionele toegang nodig hebt zonder benoemde parameters.

Hoe het werkt:

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

Wanneer meta.segments versus meta.params gebruiken

GebruikssituatieBeste aanpak
Benoemde parameters uit routepatroonmeta.params.lang
Toegang op basis van positiemeta.segments[0]
Paddiepte opvragensize(meta.segments)
Controleren of het pad een segment bevat"admin" in meta.segments

Voorbeelden met 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]

Volledige meta-objectreferentie

Het meta-object bevat alle context over de huidige aanvraag:

EigenschapTypeBeschrijving
meta.localestringHuidige locale-code (bijv. "en-US", "ko-KR", "ar-SA")
meta.paramsRecord<string, string>Routeparameters die uit het URL-patroon zijn gehaald
meta.segmentsstring[]URL-pad opgesplitst in segmenten
meta.docId`string \null`Huidige document-UUID (null voor nieuwe documenten)
meta.titlestringHuidige documenttitel

meta.locale

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

meta.params

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)

meta.segments

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"?

meta.docId

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"

meta.title

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"

documents.ref() - Gekoppelde opzoekingen

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.


Snelle referentie

Documenten ophalen

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

Contextvariabelen

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

Operatoren

// Vergelijking
==  !=  <  <=  >  >=

// Logica
&&  ||  !

// Ternair (if-else)
voorwaarde ? waardeAlsWaar : waardeAlsOnwaar

// Lidmaatschap
"waarde" in lijstOfMap

Veelvoorkomende functies

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

Foutmeldingen

Als er iets misgaat, zie je een van deze meldingen:

FoutBetekenis
SYNTAX_ERRORTypfout in je script (ontbrekend aanhalingsteken, verkeerde operator)
TYPE_ERRORJe combineert typen die niet samenwerken
RUNTIME_ERRORHet script draaide maar liep tegen een probleem aan (onbepaalde variabele)
FETCH_LIMIT_EXCEEDEDJe haalt te veel documenten op (max. 50)
TIMEOUTScript duurde te lang (max. 5 seconden)
AST_DEPTH_EXCEEDEDExpressie te diep genest (maximale diepte: 50)
SCRIPT_TOO_LONGScript overschrijdt de limiet van 5000 tekens

Uitbreidbaarheid & toekomstige mogelijkheden

De CEL-engine is ontworpen voor uitbreidbaarheid. Toekomstige mogelijkheden omvatten:

Gepland: MCP-serverintegratie

// Toekomst: externe services aanroepen via MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)

Gepland: AI-mogelijkheden

// 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.


Tips

  1. Gebruik automatische aanvulling - Typ documents. of meta. en de editor toont beschikbare opties
  2. Begin eenvoudig - Test eerst met documents.get("schema", "id") en voeg daarna .fieldName toe
  3. Controleer op null - Als een document mogelijk niet bestaat, voeg een fallback toe met != null ? ... : ...
  4. Haal niet te veel op - Elke documents.get() of documents.find() telt mee voor de limiet van 50 ophalen
  5. Geef de voorkeur aan meta.params boven meta.segments - Benoemde parameters zijn gevalideerd en betrouwbaarder
  6. Gebruik has() voor optionele parameters - Controleer has(meta.params.category) voordat je deze benadert
  7. Gebruik documents.ref() voor dynamische identificaties - Duidelijkere syntaxis wanneer het schema statisch is maar de identificatie dynamisch

Bijlage A: volledig voorbeeld van parametrische route

Deze walkthrough maakt een meertalige landingspagina die toegankelijk is via /{lang}/landingPage.

Stap 1: Maak het begroetingsdocumentschema

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"

Stap 2: Maak begroetingsdocumenten

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"
}

Stap 3: Maak de route

Maak een route met de volgende configuratie:

  • Pad: /{lang}/landingPage
  • Patroon: /{lang}/landingPage
  • Status: Live
  • Parameterbindingen:
  {
    "lang": "language"
  }

Stap 4: Voeg blokken met CEL-scripts toe

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

Stap 5: Gebruik in Next.js

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) {

Stap 6: Test de routes

Bezoek deze URL's om gelokaliseerde inhoud te zien:

URLVerwachte kop
/ko/landingPage환영합니다
/en/landingPageWelcome
/ja/landingPageようこそ

Hoe de resolutie werkt

Wanneer een gebruiker /ko/landingPage bezoekt:

  1. Routematching: CMS vindt het patroon /{lang}/landingPage
  2. Parameterextractie: meta.params.lang = "ko"
  3. Validatie: CMS valideert "ko" in het language-schema
  4. CEL-evaluatie: Scripts zoals documents.get("greeting", meta.params.lang) leveren Koreaanse inhoud op
  5. Respons: Gelokaliseerde blokken worden naar de client teruggestuurd

Bijlage B: technische referentie

CelMeta-interface (TypeScript)

interface 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) */

Parameterextractie-algoritme

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}"

Ondersteunde formaten voor parameterbinding

// Eenvoudige binding (gebruikt "code"-veld voor lookup)
{ "lang": "language" }

// Gedetailleerde binding (aangepast slug-veld)
{
  "lang": {
    "schemaName": "language",
    "slugField": "code"
  },
  "slug": {
    "schemaName": "article",
    "slugField": "slug"
  }

Prioriteit bij documentopzoeking

Bij ophalen via documents.get(schema, identifier):

  1. UUID-overeenkomst: Als identifier een geldige UUID is, haal op via id
  2. Codeveld: Controleer het veld content.code
  3. Slugveld: Controleer het veld content.slug
  4. Titelovereenkomst: Controleer het veld title

Dit biedt flexibele documentverwijzingen via elke unieke identificatie.

]
] }
:
"ようこそ"
,
"subheadline"
:
"私たちのプラットフォームへようこそ"
}
,
"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('/');
// Haal route en opgeloste parameters op
const { route, resolvedParams } = await client.routes.getRouteByPath.query({
websiteId: process.env.CMS_WEBSITE_ID!,
path,
});
// Haal blokken op voor de route
const blocks = await client.blocks.getBlocks.query({
websiteId: process.env.CMS_WEBSITE_ID!,
blockIds: route.block_ids,
// Geef opgeloste parameters door voor CEL-context
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: {},
},
});
// Render blokken
return (
<main>
{blocks.map((block) => (
<BlockRenderer
key={block.id}
block={block}
routeParams={resolvedParams}
language={resolvedParams?.lang?.value}
/>
))}
</main>
);
}
docId
:
string
|
null
;
/** Huidige documenttitel */
title: string;
}
,
"products"
]
3. Segmentaantallen moeten gelijk zijn
4. Voor elk segmentpaar:
- Als het patroon begint met : of {}, extraheer als parameter
- Anders moet het exact overeenkomen
5. Retourneer: { country: "us", lang: "en" }
}