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

Scripts dans le générateur de modèles

Un guide pratique pour écrire des expressions CEL dans le CMS.

Continue Reading
Previous‹Rendu statique avec prise en charge du mode éditionNextCreate Profound Next›

Hybride

Renderer ProjetRoutage paramétriqueTypes de composantsSseConfigurer Proxy Panneau AdminRendu statique avec prise en charge du mode éditionScripts dans le générateur de modèlesCreate Profound Next

Sans tête

Démarrage rapideJson Et Claude CodeRécupération du schéma Zod du composant

Mcp

Mcp

fonctionnalités du CMS

Feat Docs TemplateGénérateur de modèles de fonctionnalitéFonctionnalité TraducteurFonctionnalité Organisation

Motivation

Notre approche

Terminologie

Hybride Vs Sans Interface

Un guide pratique pour écrire des expressions CEL dans le CMS.


Fonctionnement de CEL

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.


Les éléments de base

Chaque expression CEL a accès à trois éléments :

ObjetCe que c'estExemple
documentsRécupérer n'importe quel document du CMSdocuments.get("country", "us")
metaInformations sur la requête en cours (locale, paramètres d'URL)meta.locale, meta.params.slug
schemaLes définitions des champs du document actuelschema.fields

Récupération de documents

La fonctionnalité la plus puissante de CEL est la récupération de documents depuis n'importe quel endroit de votre CMS.

Obtenir un document unique

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"


Utilisation des paramètres d'URL

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.


Récupérer plusieurs documents

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

Exemples concrets

Exemple 1 : titre du bloc héro provenant d'un autre document

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"


Exemple 2 : nom de pays à partir du code

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"
  • Résultat : "États-Unis"

Quand quelqu'un visite /countries/sa :

  • meta.params.code = "sa"
  • Résultat : "Arabie saoudite"

Exemple 3 : contenu conditionnel en fonction de la locale

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"


Exemple 4 : recherches enchaînées de documents

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 :

  1. documents.get("article", "us-news") renvoie { "headline": "Nouvelles des États-Unis", "countryCode": "us" }
  2. .countryCode extrait "us"
  3. documents.get("country", "us") renvoie { "code": "us", "name": "États-Unis", ... }
  4. .name extrait "États-Unis"

Résultat : "États-Unis"


Exemple 5 : valeurs de repli

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"

Exemple 6 : travailler avec des listes

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)


Routes paramétriques et meta.params

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.

Fonctionnement des paramètres de route

Définition du motif de route : Les routes utilisent la syntaxe :paramName ou {paramName} pour définir des segments dynamiques :

MotifURL d'exempleParamè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 :

  1. Extraire le segment lang de l'URL
  2. Le valider par rapport au schéma language (recherche d'un document dont content.code correspond)
  3. S'il est valide, mettre le document complet à disposition dans les paramètres résolus

Exemple : page d'atterrissage basée sur la langue

Configuration de la route :

  • Chemin : /{lang}/landingPage
  • Motif : /{lang}/landingPage
  • Liaisons de paramètres : { "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 :

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

Motif avancé : routes pays + langue

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

  1. Valide le paramètre country par rapport au schéma country
  2. Valide le paramètre lang par rapport au schéma language
  3. Valide éventuellement que lang figure dans le tableau country.languages[] (validation hiérarchique)

meta.segments - accès brut au chemin d'URL

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'URLmeta.segments
/articles/tech/ai-news["articles", "tech", "ai-news"]
/ko/landingPage["ko", "landingPage"]
/us/en/products/featured["us", "en", "products", "featured"]
/[]

Quand utiliser meta.segments plutôt que meta.params

Cas d'usageMeilleure approche
Paramètres nommés issus du motif de routemeta.params.lang
Accès basé sur la positionmeta.segments[0]
Connaître la profondeur du cheminsize(meta.segments)
Vérifier si le chemin contient un segment"admin" in meta.segments

Exemples avec 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]

Référence complète de l'objet meta

L'objet meta contient tout le contexte relatif à la requête en cours :

PropriétéTypeDescription
meta.localestringCode de locale actuel (par ex. "en-US", "ko-KR", "ar-SA")
meta.paramsRecord<string, string>Paramètres de route extraits du motif d'URL
meta.segmentsstring[]Chemin d'URL découpé en segments
meta.docIdstring \\ | nullUUID du document actuel (null pour les nouveaux documents)
meta.titlestringTitre du document actuel

meta.locale

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

meta.params

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)

meta.segments

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

meta.docId

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"

meta.title

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

documents.ref() - recherches chaînées

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.


Référence rapide

Récupération de documents

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

Variables de contexte

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

Opérateurs

// Comparaison
==  !=  <  <=  >  >=

// Logique
&&  ||  !

// Ternaire (if-else)
condition ? valeurSiVrai : valeurSiFaux

// Appartenance
"value" in listOrMap

Fonctions courantes

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

Messages d'erreur

Si quelque chose se passe mal, vous verrez l'un des messages suivants :

ErreurCe que cela signifie
SYNTAX_ERRORIl y a une faute de frappe dans votre script (guillemet manquant, mauvais opérateur)
TYPE_ERRORVous mélangez des types incompatibles
RUNTIME_ERRORLe script s'est exécuté mais a rencontré un problème (variable non définie)
FETCH_LIMIT_EXCEEDEDVous récupérez trop de documents (maximum 50)
TIMEOUTLe script a mis trop de temps (maximum 5 secondes)
AST_DEPTH_EXCEEDEDExpression trop imbriquée (profondeur max : 50)
SCRIPT_TOO_LONGLe script dépasse la limite de 5000 caractères

Extensibilité et capacités futures

Le moteur CEL est conçu pour être extensible. Les fonctionnalités prévues incluent :

Prévu : intégration du serveur MCP

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

Prévu : capacités d'IA

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


Conseils

  1. Utilisez l'autocomplétion - Tapez documents. ou meta. et l'éditeur affichera les options disponibles
  2. Commencez simplement - Testez d'abord avec documents.get("schema", "id"), puis ajoutez .fieldName
  3. Vérifiez la présence de null - Si un document peut ne pas exister, ajoutez une valeur de repli avec != null ? ... : ...
  4. Évitez les récupérations excessives - Chaque documents.get() ou documents.find() compte dans la limite de 50 récupérations
  5. Préférez meta.params à meta.segments - Les paramètres nommés sont validés et plus fiables
  6. Utilisez has() pour les paramètres optionnels - Vérifiez has(meta.params.category) avant d'y accéder
  7. Utilisez documents.ref() pour les identifiants dynamiques - Syntaxe plus claire lorsque le schéma est statique mais l'identifiant dynamique

Annexe A : exemple complet de route paramétrique

Ce guide pas à pas crée une page d'atterrissage multilingue accessible à /{lang}/landingPage.

Étape 1 : créer le schéma de document Greeting

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"

Étape 2 : créer les documents Greeting

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

Étape 3 : créer la route

Créez une route avec la configuration suivante :

  • Chemin : /{lang}/landingPage
  • Motif : /{lang}/landingPage
  • État : Live
  • Liaisons de paramètres :
  {
    "lang": "language"
  }

Étape 4 : ajouter des blocs avec des scripts CEL

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

Étape 5 : consommer dans Next.js

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

Étape 6 : tester les routes

Visitez ces URL pour voir le contenu localisé :

URLTitre attendu
/ko/landingPage환영합니다
/en/landingPageWelcome
/ja/landingPageようこそ

Fonctionnement de la résolution

Lorsqu'un utilisateur visite /ko/landingPage :

  1. Correspondance de route : le CMS fait correspondre le motif /{lang}/landingPage
  2. Extraction des paramètres : meta.params.lang = "ko"
  3. Validation : le CMS vérifie que "ko" existe dans le schéma language
  4. Évaluation CEL : les scripts comme documents.get("greeting", meta.params.lang) renvoient le contenu en coréen
  5. Réponse : les blocs localisés sont renvoyés au client

Annexe B : référence technique

Interface CelMeta (TypeScript)

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

Algorithme d'extraction des paramètres

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"

Formats de liaison de paramètres pris en charge

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

Priorité de recherche des documents

Lors d'une récupération via documents.get(schema, identifier) :

  1. Correspondance UUID : si l'identifiant est un UUID valide, récupération par id
  2. Champ code : vérifie le champ content.code
  3. Champ slug : vérifie le champ content.slug
  4. Correspondance de titre : vérifie le champ title

Cela permet des références de documents flexibles via n'importe quel identifiant unique.

]
] }
:
"ようこそ"
,
"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('/');
// Obtenir la route et les paramètres résolus
const { route, resolvedParams } = await client.routes.getRouteByPath.query({
websiteId: process.env.CMS_WEBSITE_ID!,
path,
});
// Obtenir les blocs pour la route
const blocks = await client.blocks.getBlocks.query({
websiteId: process.env.CMS_WEBSITE_ID!,
blockIds: route.block_ids,
// Transmettre les paramètres résolus pour le contexte CEL
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: {},
},
});
// Afficher les blocs
return (
<main>
{blocks.map((block) => (
<BlockRenderer
key={block.id}
block={block}
routeParams={resolvedParams}
language={resolvedParams?.lang?.value}
/>
))}
</main>
);
}
docId
:
string
|
null
;
/** Current document title */
title: string;
}
]
3. Faire correspondre le nombre de segments (doit être identique)
4. Pour chaque paire de segments :
- Si le motif commence par : ou {}, extraire comme paramètre
- Sinon, correspondance exacte obligatoire
5. Retourner : { country: "us", lang: "en" }
}