Una guida pratica alla scrittura di espressioni CEL nel CMS.
Una guida pratica alla scrittura di espressioni CEL nel CMS.
CEL (Common Expression Language) è un linguaggio di scripting leggero integrato nel nostro CMS. Ti permette di scrivere espressioni dinamiche che possono recuperare dati dai documenti, leggere i parametri dell'URL e calcolare valori al volo.
Ecco che cosa succede quando viene eseguito uno script CEL:
Il tuo script Il motore Risultato
| | |
v v v
documents.get("article", "intro") --> Recupera dal database --> { headline: "Benvenuto", body: "..." }
.headline --> Estrae il campo --> "Benvenuto"
Considera CEL come un linguaggio di query in sola lettura. Non può modificare nulla nel database: si limita a leggere i dati e restituire un risultato calcolato. Questo lo rende sicuro da utilizzare ovunque nel CMS.
Ogni espressione CEL ha accesso a tre elementi:
| Oggetto | Che cos'è | Esempio |
|---|---|---|
documents | Recupera qualsiasi documento dal CMS | documents.get("country", "us") |
meta | Informazioni sulla richiesta corrente (locale, parametri URL) | meta.locale, meta.params.slug |
schema | Le definizioni dei campi del documento corrente | schema.fields |
La funzione più potente di CEL è il recupero di documenti da qualsiasi punto del tuo CMS.
Sintassi: documents.get(schemaName, identifier)
Supponiamo che tu abbia un documento article archiviato con l'identificatore "welcome-post":
// Archiviato nel CMS come: article / welcome-post
{
"headline": "Benvenuti sulla nostra piattaforma",
"author": "Sarah Chen",
"body": "Siamo entusiasti di annunciare...",
"tags": ["annuncio", "notizie"]
}
Per recuperare l'intero documento:
documents.get("article", "welcome-post")
Restituisce:
{
"headline": "Benvenuti sulla nostra piattaforma",
"author": "Sarah Chen",
"body": "Siamo entusiasti di annunciare...",
"tags": ["annuncio", "notizie"]
}
Per recuperare solo il titolo:
documents.get("article", "welcome-post").headline
Restituisce: "Benvenuti sulla nostra piattaforma"
Per recuperare l'autore:
documents.get("article", "welcome-post").author
Restituisce: "Sarah Chen"
Quando la tua pagina ha route dinamiche (come /articles/[slug]), puoi usare meta.params per ottenere il parametro dell'URL e recuperare il documento corretto.
Se qualcuno visita /articles/welcome-post:
documents.get("article", meta.params.slug).headline
Restituisce: "Benvenuti sulla nostra piattaforma"
In questo modo crei pagine dinamiche: lo stesso script CEL funziona per qualsiasi articolo, utilizzando semplicemente lo slug presente nell'URL.
Sintassi: documents.find(schemaName) oppure documents.find(schemaName, filter)
// Recupera tutti i paesi
documents.find("country")
Restituisce:
[
{ "code": "us", "name": "Stati Uniti", "flag": "US" },
{ "code": "sa", "name": "Arabia Saudita", "flag": "SA" },
{ "code": "gb", "name": "Regno Unito", "flag": "GB" }
// Recupera i paesi con un filtro
documents.find("country", { "where": { "code": "us" } })
Restituisce:
[
{ "code": "us", "name": "Stati Uniti", "flag": "US" }
]
Hai un hero-block che dovrebbe mostrare un titolo preso da un documento article.
Il tuo documento article (identificatore: "homepage-hero"):
{
"headline": "Sviluppa più velocemente, distribuisci in modo più intelligente",
"subheadline": "Il CMS moderno per gli sviluppatori"
}
Script CEL nel campo titolo dell'hero block:
documents.get("article", "homepage-hero").headline
Risultato: l'hero mostra "Sviluppa più velocemente, distribuisci in modo più intelligente"
Stai creando una pagina in /countries/[code] e vuoi mostrare il nome completo del paese.
I tuoi documenti country:
// country / us
{ "code": "us", "name": "Stati Uniti", "flag": "US", "languages": ["en", "es"] }
// country / sa
{ "code": "sa", "name": "Arabia Saudita", "flag": "SA", "languages": ["ar", "en"
Script CEL:
documents.get("country", meta.params.code).name
Quando qualcuno visita /countries/us:
meta.params.code = "us""Stati Uniti"Quando qualcuno visita /countries/sa:
meta.params.code = "sa""Arabia Saudita"Mostra titoli diversi in base al locale dell'utente.
meta.locale == "ar-SA" ? "مرحبا بكم" : "Benvenuto"
Se il locale è "ar-SA": restituisce "مرحبا بكم"
Se il locale è qualsiasi altro: restituisce "Benvenuto"
Il tuo article ha un campo countryCode e vuoi ottenere il nome completo del paese.
Documento article:
{ "headline": "Notizie dagli Stati Uniti", "countryCode": "us" }
Script CEL:
documents.get("country", documents.get("article", "us-news").countryCode).name
Cosa succede:
documents.get("article", "us-news") restituisce { "headline": "Notizie dagli Stati Uniti", "countryCode": "us" }.countryCode estrae "us"documents.get("country", "us") restituisce { "code": "us", "name": "Stati Uniti", ... }.name estrae "Stati Uniti"Risultato: "Stati Uniti"
Se un documento potrebbe non esistere, puoi fornire un fallback:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Articolo non trovato"
Oppure verifica se esiste un campo specifico:
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "Autore sconosciuto"
Il tuo articolo ha dei tag e vuoi verificare se esiste un tag specifico:
"in primo piano" in documents.get("article", "welcome-post").tags
Restituisce: true se l'articolo ha il tag "in primo piano"
Ottieni il primo tag:
documents.get("article", "welcome-post").tags[0]
Restituisce: "annuncio" (il primo tag)
Conta i tag:
size(documents.get("article", "welcome-post").tags)
Restituisce: 2 (numero di tag)
Le route parametriche sono la chiave per creare pagine dinamiche e localizzate. Quando definisci un pattern di route come /{lang}/landingPage, il CMS estrae i parametri dall'URL e li rende disponibili tramite meta.params.
Definizione del pattern di route:
Le route usano la sintassi :paramName o {paramName} per definire segmenti dinamici:
| Pattern | URL di esempio | Parametri estratti |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
Associazioni dei parametri: Ogni parametro di route può essere associato a uno schema di documento per la convalida:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
Questa associazione indica al CMS:
lang dall'URLlanguage (cerca un documento in cui content.code corrisponde)Configurazione della route:
/{lang}/landingPage/{lang}/landingPage{ "lang": "language" }I tuoi documenti greeting:
// greeting / ko
{ "code": "ko", "headline": "환영합니다", "subheadline": "우리 플랫폼에 오신 것을 환영합니다" }
// greeting / en
{ "code": "en", "headline": "Benvenuto", "subheadline": "Benvenuto sulla nostra piattaforma" }
// greeting / ja
{ "code": "ja",
Script CEL per recuperare il contenuto localizzato:
documents.get("greeting", meta.params.lang).headline
Come si risolve:
| URL | meta.params.lang | Risultato |
|---|---|---|
/ko/landingPage | "ko" | "환영합니다" |
/en/landingPage | "en" | "Benvenuto" |
/ja/landingPage | "ja" | "ようこそ" |
Per route come /{country}/{lang}/products:
Configurazione della route:
{
"pattern": "/{country}/{lang}/products",
"param_bindings": {
"country": "country",
"lang": "language"
}
}
Script CEL:
// Ottieni il nome del paese
documents.get("country", meta.params.country).name
// Ottieni l'elenco di prodotti localizzato in base al paese
documents.find("product", { "where": { "country": meta.params.country } })
// Combinato: mostra un saluto specifico del paese nella lingua dell'utente
documents.get("greeting", meta.params.lang).headline + " da " + documents.get("country", meta.params.country).name
Cascata di convalida:
Il CMS convalida i parametri in modo gerarchico. Per le route /{country}/{lang}:
country rispetto allo schema countrylang rispetto allo schema languagelang sia presente nell'array country.languages[] (convalida gerarchica)meta.segments fornisce il percorso URL grezzo come array, utile quando hai bisogno di un accesso posizionale senza parametri nominati.
Come funziona:
| Percorso URL | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
| Caso d'uso | Approccio migliore |
|---|---|
| Parametri nominati dal pattern della route | meta.params.lang |
| Accesso basato sulla posizione | meta.segments[0] |
| Ottenere la profondità del percorso | size(meta.segments) |
| Verificare se il percorso contiene un segmento | "admin" in meta.segments |
// Ottieni il primo segmento (spesso il codice della lingua)
meta.segments[0]
// Controlla la profondità del percorso
size(meta.segments) > 2 ? "profondo" : "superficiale"
// Verifica se siamo nella sezione admin
"admin" in meta.segments ? "modalità admin" : "modalità pubblica"
// Fallback: usa il segmento se il parametro non è associato
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
L'oggetto meta contiene tutto il contesto sulla richiesta corrente:
| Proprietà | Tipo | Descrizione | |
|---|---|---|---|
meta.locale | string | Codice locale corrente (ad es. "en-US", "ko-KR", "ar-SA") | |
meta.params | Record<string, string> | Parametri di route estratti dal pattern URL | |
meta.segments | string[] | Percorso URL suddiviso in segmenti | |
meta.docId | `string \ | null` | UUID del documento corrente (null per i nuovi documenti) |
meta.title | string | Titolo del documento corrente |
Il codice locale segue il formato BCP 47 (lingua-regione):
// Controlla se il locale è RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// Ottieni solo la parte della lingua
meta.locale.split("-")[0] // Non supportato - usa invece meta.params.lang
I parametri di route sono sempre stringhe. Il CMS li convalida rispetto agli schemi associati prima dell'esecuzione:
// Accedi al parametro nominale
meta.params.lang // "ko"
meta.params.country // "us"
meta.params.slug // "welcome-post"
// Verifica se il parametro esiste
has(meta.params.category) // true/false
// Usa nel recupero dei documenti
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
Segmenti grezzi dell'URL come array:
// Accedi tramite indice (partendo da 0)
meta.segments[0] // Primo segmento
meta.segments[1] // Secondo segmento
// Verifica la lunghezza
size(meta.segments) // Numero di segmenti
// Verifica la presenza
"products" in meta.segments // Il percorso include "products"?
L'UUID del documento corrente, utile per script autoreferenziali:
// Disponibile solo durante la modifica di documenti esistenti
meta.docId != null ? "modifica" : "creazione"
// Usa nella logica condizionale
meta.docId != null ? documents.get("article", meta.docId).status : "bozza"
Il titolo del documento corrente:
// Usa per la visualizzazione
"Modifica: " + meta.title
// Condizione basata sul titolo
meta.title.contains("Bozza") ? "in lavorazione" : "pubblicato"
Per una sintassi più pulita quando lo schema è noto ma l'identificatore è dinamico:
// Approccio tradizionale
documents.get("airports", meta.params.code).name
// Uso di ref() - schema separato dall'identificatore dinamico
documents.ref("airports").get(meta.params.code).name
Entrambi sono equivalenti, ma ref() rende più chiara la parte dinamica.
documents.get("schema", "identifier") // Recupera un documento
documents.get("schema", "id").fieldName // Recupera un campo specifico
documents.find("schema") // Recupera tutti i documenti
documents.find("schema", { "where": {...}}) // Query filtrata
documents.ref("schema").get(identifier) // Ricerca concatenata
meta.locale // "en-US", "ar-SA", ecc.
meta.params.xyz // Parametro URL chiamato "xyz"
meta.segments // Percorso URL come array: ["articles", "intro"]
meta.segments[0] // Primo segmento del percorso
meta.docId // ID del documento corrente (oppure null)
meta.title // Titolo del documento corrente
// Confronto
== != < <= > >=
// Logica
&& || !
// Operatore ternario (if-else)
condition ? valueIfTrue : valueIfFalse
// Appartenenza
"value" in listOrMap
size(list) // Conta gli elementi
size(string) // Lunghezza della stringa
"testo".startsWith("te") // true
"testo".endsWith("to") // true
"testo".contains("es") // true
has(object.property) // Verifica se la proprietà esiste
Se qualcosa va storto, vedrai uno di questi messaggi:
| Errore | Cosa significa |
|---|---|
SYNTAX_ERROR | C'è un refuso nello script (virgolette mancanti, operatore errato) |
TYPE_ERROR | Stai mescolando tipi che non funzionano insieme |
RUNTIME_ERROR | Lo script è stato eseguito ma ha incontrato un problema (variabile non definita) |
FETCH_LIMIT_EXCEEDED | Stai recuperando troppi documenti (massimo 50) |
TIMEOUT | Lo script ha impiegato troppo tempo (massimo 5 secondi) |
AST_DEPTH_EXCEEDED | Espressione troppo annidata (profondità massima: 50) |
SCRIPT_TOO_LONG | Lo script supera il limite di 5000 caratteri |
Il motore CEL è progettato per l'estensibilità. Le funzionalità future previste includono:
// Futuro: chiamare servizi esterni tramite MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// Futuro: generazione di contenuti basata sull'AI
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"])
Queste funzionalità verranno aggiunte tramite il sistema di funzioni registrate, mantenendo la retrocompatibilità con gli script esistenti.
documents. o meta. e l'editor mostrerà le opzioni disponibilidocuments.get("schema", "id"), poi aggiungi .fieldName!= null ? ... : ...documents.get() o documents.find() conta ai fini del limite di 50 recuperihas(meta.params.category) prima di accedereQuesto walkthrough crea una landing page multilingue accessibile all'indirizzo /{lang}/landingPage.
Nel pannello di amministrazione del CMS, crea uno schema personalizzato chiamato greeting:
{
"name": "greeting",
"fields": [
{ "name": "code", "type": "string", "required": true },
{ "name": "headline", "type": "string", "required": true },
{ "name": "subheadline"
Crea documenti per ogni lingua:
Documento: greeting / ko
{
"code": "ko",
"headline": "환영합니다",
"subheadline": "우리 플랫폼에 오신 것을 환영합니다",
"ctaText": "시작하기",
"ctaUrl": "/ko/get-started"
}
Documento: greeting / en
{
"code": "en",
"headline": "Benvenuto",
"subheadline": "Benvenuto sulla nostra piattaforma",
"ctaText": "Inizia",
"ctaUrl": "/en/get-started"
}
Documento: greeting / ja
{
"code": "ja",
"headline": "ようこそ",
"subheadline": "私たちのプラットフォームへようこそ",
"ctaText": "始める",
"ctaUrl": "/ja/get-started"
}
Crea una route con la seguente configurazione:
/{lang}/landingPage/{lang}/landingPage {
"lang": "language"
}
Aggiungi un hero block alla route con questi script CEL per ciascun campo:
Campo titolo:
documents.get("greeting", meta.params.lang).headline
Campo sottotitolo:
documents.get("greeting", meta.params.lang).subheadline
Campo testo CTA:
documents.get("greeting", meta.params.lang).ctaText
Campo URL CTA:
documents.get("greeting", meta.params.lang).ctaUrl
Crea una route catch-all nella tua app Next.js:
// app/[...slug]/page.tsx
import { getCmsClient } from '@repo/renderer';
interface PageProps {
params: { slug: string[] };
}
export default async function Page({ params }: PageProps) {
Visita questi URL per vedere il contenuto localizzato:
| URL | Titolo atteso |
|---|---|
/ko/landingPage | 환영합니다 |
/en/landingPage | Benvenuto |
/ja/landingPage | ようこそ |
Quando un utente visita /ko/landingPage:
/{lang}/landingPagemeta.params.lang = "ko"languagedocuments.get("greeting", meta.params.lang) restituiscono il contenuto in coreanointerface CelMeta {
/** Codice locale corrente (ad esempio 'en-US') */
locale: string;
/** Parametri di route estratti dall'URL */
params: Record<string, string>;
/** Segmenti del percorso URL */
segments: string[];
/** ID del documento corrente (se si modifica un documento esistente) */
La funzione extractParams elabora i percorsi URL:
Pattern: /{country}/{lang}/products
Path: /us/en/products
Algorithm:
1. Normalizza entrambi (rimuovi gli slash finali)
2. Suddividi in segmenti: ["us", "en", "products"] e ["{country}", "{lang}"
// Associazione semplice (usa il campo "code" per la ricerca)
{ "lang": "language" }
// Associazione dettagliata (campo slug personalizzato)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
Quando si recupera tramite documents.get(schema, identifier):
idcontent.codecontent.slugtitleQuesto consente riferimenti flessibili ai documenti tramite qualsiasi identificatore univoco.