Un guide pratique pour écrire des expressions CEL dans le CMS.
Un guide pratique pour écrire des expressions CEL dans le CMS.
CEL (Common Expression Language) est un langage de script léger intégré à notre CMS. Il vous permet d'écrire des expressions dynamiques qui peuvent extraire des données des documents, lire les paramètres d'URL et calculer des valeurs à la volée.
Voici ce qui se passe lorsqu'un script CEL s'exécute :
Votre script Le moteur Résultat
| | |
v v v
documents.get("article", "intro") --> Récupère depuis la base de données --> { headline: "Bienvenue", body: "..." }
.headline --> Extrait le champ --> "Bienvenue"
Considérez CEL comme un langage de requête en lecture seule. Il ne peut rien modifier dans la base de données : il se contente de lire les données et de renvoyer un résultat calculé. Cela le rend sûr à utiliser partout dans le CMS.
Chaque expression CEL a accès à trois éléments :
| Objet | Ce que c'est | Exemple |
|---|---|---|
documents | Récupérer n'importe quel document du CMS | documents.get("country", "us") |
meta | Informations sur la requête en cours (locale, paramètres d'URL) | meta.locale, meta.params.slug |
schema | Les définitions des champs du document actuel | schema.fields |
La fonctionnalité la plus puissante de CEL est la récupération de documents depuis n'importe quel endroit de votre CMS.
Syntaxe : documents.get(schemaName, identifier)
Supposons que vous disposiez d'un document article stocké avec l'identifiant "welcome-post" :
// Stocké dans le CMS sous : article / welcome-post
{
"headline": "Bienvenue sur notre plateforme",
"author": "Sarah Chen",
"body": "Nous sommes ravis d'annoncer...",
"tags": ["annonce", "actualités"]
}
Pour récupérer le document entier :
documents.get("article", "welcome-post")
Renvoie :
{
"headline": "Bienvenue sur notre plateforme",
"author": "Sarah Chen",
"body": "Nous sommes ravis d'annoncer...",
"tags": ["annonce", "actualités"]
}
Pour récupérer uniquement le titre :
documents.get("article", "welcome-post").headline
Renvoie : "Bienvenue sur notre plateforme"
Pour récupérer l'autrice :
documents.get("article", "welcome-post").author
Renvoie : "Sarah Chen"
Lorsque votre page possède des routes dynamiques (comme /articles/[slug]), vous pouvez utiliser meta.params pour obtenir le paramètre d'URL et récupérer le bon document.
Si quelqu'un visite /articles/welcome-post :
documents.get("article", meta.params.slug).headline
Renvoie : "Bienvenue sur notre plateforme"
C'est ainsi que vous créez des pages dynamiques : le même script CEL fonctionne pour n'importe quel article, en utilisant simplement le slug présent dans l'URL.
Syntaxe : documents.find(schemaName) ou documents.find(schemaName, filter)
// Récupérer tous les pays
documents.find("country")
Renvoie :
[
{ "code": "us", "name": "États-Unis", "flag": "US" },
{ "code": "sa", "name": "Arabie saoudite", "flag": "SA" },
{ "code": "gb", "name": "Royaume-Uni", "flag": "GB" }
// Récupérer des pays avec un filtre
documents.find("country", { "where": { "code": "us" } })
Renvoie :
[
{ "code": "us", "name": "États-Unis", "flag": "US" }
]
Vous disposez d'un hero-block qui doit afficher un titre extrait d'un document article.
Votre document d'article (identifiant : "homepage-hero") :
{
"headline": "Construisez plus vite, livrez plus intelligemment",
"subheadline": "Le CMS moderne pour les développeurs"
}
Script CEL dans le champ de titre du bloc héro :
documents.get("article", "homepage-hero").headline
Résultat : Le bloc héro affiche "Construisez plus vite, livrez plus intelligemment"
Vous créez une page à /countries/[code] et souhaitez afficher le nom complet du pays.
Vos documents de pays :
// country / us
{ "code": "us", "name": "États-Unis", "flag": "US", "languages": ["en", "es"] }
// country / sa
{ "code": "sa", "name": "Arabie saoudite", "flag": "SA", "languages": ["ar", "en"
Script CEL :
documents.get("country", meta.params.code).name
Quand quelqu'un visite /countries/us :
meta.params.code = "us"Quand quelqu'un visite /countries/sa :
meta.params.code = "sa"Affichez des titres différents selon la locale de l'utilisateur.
meta.locale == "ar-SA" ? "مرحبا بكم" : "Bienvenue"
Si la locale est "ar-SA" : renvoie "مرحبا بكم"
Si la locale est différente : renvoie "Bienvenue"
Votre article possède un champ countryCode, et vous souhaitez obtenir le nom complet du pays.
Document d'article :
{ "headline": "Nouvelles des États-Unis", "countryCode": "us" }
Script CEL :
documents.get("country", documents.get("article", "us-news").countryCode).name
Ce qui se passe :
documents.get("article", "us-news") renvoie { "headline": "Nouvelles des États-Unis", "countryCode": "us" }.countryCode extrait "us"documents.get("country", "us") renvoie { "code": "us", "name": "États-Unis", ... }.name extrait "États-Unis"Résultat : "États-Unis"
Si un document peut ne pas exister, vous pouvez prévoir une valeur de repli :
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Article introuvable"
Ou vérifier si un champ spécifique existe :
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "Auteur inconnu"
Votre article possède des balises et vous souhaitez vérifier si une balise spécifique existe :
"featured" in documents.get("article", "welcome-post").tags
Renvoie : true si l'article possède la balise "featured"
Obtenir la première balise :
documents.get("article", "welcome-post").tags[0]
Renvoie : "annonce" (la première balise)
Compter les balises :
size(documents.get("article", "welcome-post").tags)
Renvoie : 2 (nombre de balises)
Les routes paramétriques sont la clé pour créer des pages dynamiques et localisées. Lorsque vous définissez un motif de route comme /{lang}/landingPage, le CMS extrait les paramètres de l'URL et les met à disposition via meta.params.
Définition du motif de route :
Les routes utilisent la syntaxe :paramName ou {paramName} pour définir des segments dynamiques :
| Motif | URL d'exemple | Paramètres extraits |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
Liaisons de paramètres : Chaque paramètre de route peut être lié à un schéma de document pour la validation :
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
Cette liaison indique au CMS :
lang de l'URLlanguage (recherche d'un document dont content.code correspond)Configuration de la route :
/{lang}/landingPage/{lang}/landingPage{ "lang": "language" }Vos documents de bienvenue :
// greeting / ko
{ "code": "ko", "headline": "환영합니다", "subheadline": "우리 플랫폼에 오신 것을 환영합니다" }
// greeting / en
{ "code": "en", "headline": "Welcome", "subheadline": "Welcome to our platform" }
// greeting / ja
{ "code": "ja", "headline"
Script CEL pour récupérer le contenu localisé :
documents.get("greeting", meta.params.lang).headline
Comment il se résout :
| URL | meta.params.lang | Résultat |
|---|---|---|
/ko/landingPage | "ko" | "환영합니다" |
/en/landingPage | "en" | "Welcome" |
/ja/landingPage | "ja" | "ようこそ" |
Pour des routes telles que /{country}/{lang}/products :
Configuration de la route :
{
"pattern": "/{country}/{lang}/products",
"param_bindings": {
"country": "country",
"lang": "language"
}
}
Scripts CEL :
// Obtenir le nom du pays
documents.get("country", meta.params.country).name
// Obtenir la liste de produits localisée selon le pays
documents.find("product", { "where": { "country": meta.params.country } })
// Combiné : afficher un message de bienvenue spécifique au pays dans la langue de l'utilisateur
documents.get("greeting", meta.params.lang).headline + " de la part de " + documents.get("country", meta.params.country).name
Cascade de validation :
Le CMS valide les paramètres de manière hiérarchique. Pour les routes /{country}/{lang} :
country par rapport au schéma countrylang par rapport au schéma languagelang figure dans le tableau country.languages[] (validation hiérarchique)meta.segments fournit le chemin d'URL brut sous forme de tableau, pratique lorsque vous avez besoin d'un accès positionnel sans paramètres nommés.
Fonctionnement :
| Chemin d'URL | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
| Cas d'usage | Meilleure approche |
|---|---|
| Paramètres nommés issus du motif de route | meta.params.lang |
| Accès basé sur la position | meta.segments[0] |
| Connaître la profondeur du chemin | size(meta.segments) |
| Vérifier si le chemin contient un segment | "admin" in meta.segments |
// Obtenir le premier segment (souvent le code de langue)
meta.segments[0]
// Vérifier la profondeur du chemin
size(meta.segments) > 2 ? "profond" : "peu profond"
// Vérifier si nous sommes dans la section admin
"admin" in meta.segments ? "mode admin" : "mode public"
// Repli : utiliser le segment si le paramètre n'est pas lié
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
L'objet meta contient tout le contexte relatif à la requête en cours :
| Propriété | Type | Description | |
|---|---|---|---|
meta.locale | string | Code de locale actuel (par ex. "en-US", "ko-KR", "ar-SA") | |
meta.params | Record<string, string> | Paramètres de route extraits du motif d'URL | |
meta.segments | string[] | Chemin d'URL découpé en segments | |
meta.docId | string \\ | null | UUID du document actuel (null pour les nouveaux documents) | |
meta.title | string | Titre du document actuel |
Le code de locale suit le format BCP 47 (langue-région) :
// Vérifier la locale pour les langues RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// Obtenir uniquement la partie langue
meta.locale.split("-")[0] // Non pris en charge - utilisez plutôt meta.params.lang
Les paramètres de route sont toujours des chaînes. Le CMS les valide par rapport aux schémas liés avant l'évaluation :
// Accéder à un paramètre nommé
meta.params.lang // "ko"
meta.params.country // "us"
meta.params.slug // "welcome-post"
// Vérifier si un paramètre existe
has(meta.params.category) // true/false
// Utiliser dans une récupération de document
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
Segments d'URL bruts sous forme de tableau :
// Accès par index (base 0)
meta.segments[0] // Premier segment
meta.segments[1] // Deuxième segment
// Vérifier la longueur
size(meta.segments) // Nombre de segments
// Vérifier l'appartenance
"products" in meta.segments // Le chemin contient-il "products" ?
L'UUID du document actuel, utile pour les scripts auto-référentiels :
// Uniquement disponible lors de l'édition de documents existants
meta.docId != null ? "édition en cours" : "création en cours"
// Utiliser dans une logique conditionnelle
meta.docId != null ? documents.get("article", meta.docId).status : "draft"
Le titre du document actuel :
// Utiliser pour l'affichage
"Modification : " + meta.title
// Conditionnel basé sur le titre
meta.title.contains("Draft") ? "travail en cours" : "publié"
Pour une syntaxe plus claire lorsque le schéma est connu mais l'identifiant dynamique :
// Approche traditionnelle
documents.get("airports", meta.params.code).name
// Utiliser ref() - schéma séparé de l'identifiant dynamique
documents.ref("airports").get(meta.params.code).name
Les deux sont équivalentes, mais ref() rend la partie dynamique plus explicite.
documents.get("schema", "identifier") // Récupérer un document
documents.get("schema", "id").fieldName // Récupérer un champ spécifique
documents.find("schema") // Récupérer tous les documents
documents.find("schema", { "where": {...}}) // Requête filtrée
documents.ref("schema").get(identifier) // Recherche chaînée
meta.locale // "en-US", "ar-SA", etc.
meta.params.xyz // Paramètre d'URL nommé "xyz"
meta.segments // Chemin d'URL sous forme de tableau : ["articles", "intro"]
meta.segments[0] // Premier segment du chemin
meta.docId // ID du document actuel (ou null)
meta.title // Titre du document actuel
// Comparaison
== != < <= > >=
// Logique
&& || !
// Ternaire (if-else)
condition ? valeurSiVrai : valeurSiFaux
// Appartenance
"value" in listOrMap
size(list) // Compter les éléments
size(string) // Longueur d'une chaîne
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // Vérifier si la propriété existe
Si quelque chose se passe mal, vous verrez l'un des messages suivants :
| Erreur | Ce que cela signifie |
|---|---|
SYNTAX_ERROR | Il y a une faute de frappe dans votre script (guillemet manquant, mauvais opérateur) |
TYPE_ERROR | Vous mélangez des types incompatibles |
RUNTIME_ERROR | Le script s'est exécuté mais a rencontré un problème (variable non définie) |
FETCH_LIMIT_EXCEEDED | Vous récupérez trop de documents (maximum 50) |
TIMEOUT | Le script a mis trop de temps (maximum 5 secondes) |
AST_DEPTH_EXCEEDED | Expression trop imbriquée (profondeur max : 50) |
SCRIPT_TOO_LONG | Le script dépasse la limite de 5000 caractères |
Le moteur CEL est conçu pour être extensible. Les fonctionnalités prévues incluent :
// Futur : appeler des services externes via MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// Futur : génération de contenu alimentée par l'IA
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"])
Ces fonctionnalités seront ajoutées via le système de fonctions enregistrées, en maintenant la rétrocompatibilité avec les scripts existants.
documents. ou meta. et l'éditeur affichera les options disponiblesdocuments.get("schema", "id"), puis ajoutez .fieldName!= null ? ... : ...documents.get() ou documents.find() compte dans la limite de 50 récupérationshas(meta.params.category) avant d'y accéderCe guide pas à pas crée une page d'atterrissage multilingue accessible à /{lang}/landingPage.
Dans l'administration du CMS, créez un schéma personnalisé appelé greeting :
{
"name": "greeting",
"fields": [
{ "name": "code", "type": "string", "required": true },
{ "name": "headline", "type": "string", "required": true },
{ "name": "subheadline"
Créez des documents pour chaque langue :
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": "Get Started",
"ctaUrl": "/en/get-started"
}
Document : greeting / ja
{
"code": "ja",
"headline": "ようこそ",
"subheadline": "私たちのプラットフォームへようこそ",
"ctaText": "始める",
"ctaUrl": "/ja/get-started"
}
Créez une route avec la configuration suivante :
/{lang}/landingPage/{lang}/landingPage {
"lang": "language"
}
Ajoutez un bloc héro à la route avec ces scripts CEL pour chaque champ :
Champ Headline :
documents.get("greeting", meta.params.lang).headline
Champ Subheadline :
documents.get("greeting", meta.params.lang).subheadline
Champ CTA Text :
documents.get("greeting", meta.params.lang).ctaText
Champ CTA URL :
documents.get("greeting", meta.params.lang).ctaUrl
Créez une route catch-all dans votre application Next.js :
// app/[...slug]/page.tsx
import { getCmsClient } from '@repo/renderer';
interface PageProps {
params: { slug: string[] };
}
export default async function Page({ params }: PageProps) {
Visitez ces URL pour voir le contenu localisé :
| URL | Titre attendu |
|---|---|
/ko/landingPage | 환영합니다 |
/en/landingPage | Welcome |
/ja/landingPage | ようこそ |
Lorsqu'un utilisateur visite /ko/landingPage :
/{lang}/landingPagemeta.params.lang = "ko"languagedocuments.get("greeting", meta.params.lang) renvoient le contenu en coréeninterface CelMeta {
/** Current locale code (e.g., 'en-US') */
locale: string;
/** Route parameters extracted from URL */
params: Record<string, string>;
/** URL path segments */
segments: string[];
/** Current document ID (if editing existing document) */
La fonction extractParams traite les chemins d'URL :
Motif : /{country}/{lang}/products
Chemin : /us/en/products
Algorithme :
1. Normaliser les deux (supprimer les barres obliques finales)
2. Découper en segments : ["us", "en", "products"] et ["{country}", "{lang}", "products"
// Liaison simple (utilise le champ "code" pour la recherche)
{ "lang": "language" }
// Liaison détaillée (champ slug personnalisé)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
Lors d'une récupération via documents.get(schema, identifier) :
idcontent.codecontent.slugtitleCela permet des références de documents flexibles via n'importe quel identifiant unique.