Guía práctica para escribir expresiones CEL en el CMS.
Guía práctica para escribir expresiones CEL en el CMS.
CEL (Common Expression Language, o Lenguaje Común de Expresiones) es un lenguaje de scripting ligero integrado en nuestro CMS. Permite escribir expresiones dinámicas que obtienen datos de documentos, leen parámetros de URL y calculan valores al vuelo.
Esto ocurre cuando se ejecuta un script CEL:
Tu script El motor Resultado
| | |
v v v
documents.get("article", "intro") --> Obtiene de la base de datos --> { headline: "Welcome", body: "..." }
.headline --> Extrae el campo --> "Welcome"
Piensa en CEL como un lenguaje de consultas de solo lectura. No puede modificar nada en la base de datos: únicamente lee datos y devuelve un resultado calculado. Esto hace que sea seguro usarlo en cualquier parte del CMS.
Cada expresión CEL tiene acceso a tres elementos:
| Objeto | Qué es | Ejemplo |
|---|---|---|
documents | Obtiene cualquier documento del CMS | documents.get("country", "us") |
meta | Información sobre la solicitud actual (locale y parámetros de URL) | meta.locale, meta.params.slug |
schema | Definiciones de campos del documento actual | schema.fields |
docAl escribir expresiones CEL dentro del editor de documentos, puedes acceder a los valores de los campos del documento actual mediante el objeto doc. Esto permite crear campos calculados y referencias entre campos.
// Acceder al campo de precio del documento actual
doc.price
// Calcular el total a partir de los campos del documento actual
doc.price * doc.quantity
// Condicional basado en el estado del documento actual
doc.status == "published" ? doc.title : "Borrador: " + doc.title
El objeto doc contiene todos los valores de los campos del documento editado. Es útil para:
doc.price * doc.quantity)La función más potente de CEL es obtener documentos desde cualquier lugar del CMS.
Sintaxis: documents.get(schemaName, identifier)
Supongamos que tienes un documento article almacenado con el identificador "welcome-post":
// Almacenado en el CMS como: article / welcome-post
{
"headline": "Welcome to Our Platform",
"author": "Sarah Chen",
"body": "We're excited to announce...",
"tags": ["announcement", "news"]
}
Para obtener el documento completo:
documents.get("article", "welcome-post")
Para obtener solo el titular:
documents.get("article", "welcome-post").headline
Devuelve: "Welcome to Our Platform"
Para obtener el autor:
documents.get("article", "welcome-post").author
Devuelve: "Sarah Chen"
Cuando tu página tiene rutas dinámicas, como /articles/[slug], puedes usar meta.params para obtener el parámetro de URL y recuperar el documento correcto.
Si alguien visita /articles/welcome-post:
documents.get("article", meta.params.slug).headline
Esto permite crear páginas dinámicas: el mismo script CEL funciona para cualquier artículo, usando el slug presente en la URL.
Sintaxis: documents.find(schemaName) o documents.find(schemaName, filter)
// Obtener todos los países
documents.find("country")
// Obtener países con un filtro
documents.find("country", { "where": { "code": "us" } })
CEL permite obtener contenido traducido de documentos de dos formas: traducción automática basada en el locale y búsqueda explícita de traducciones.
Cuando meta.locale está definido, documents.get() combina automáticamente el contenido traducido:
// Si meta.locale es "fr", devuelve la traducción francesa combinada con el documento base
documents.get("greeting", "welcome").headline
Cómo funciona:
meta.locale no es "en" ni "en-US", busca la traducción en la tabla translations.{ ...baseContent, ...translatedContent }.Los campos traducidos sobrescriben los campos base; los campos sin traducir usan el documento base.
Para obtener una traducción específica independientemente del locale actual:
Sintaxis: documents.translated(schemaName, identifier, locale)
// Obtener siempre la traducción al español
documents.translated("greeting", "welcome", "es").headline
// Obtener una traducción según un parámetro de URL
documents.translated("product", meta.params.id, meta.params.lang).description
// Comparar traducciones
documents.translated("article", "intro", "en").title + " / " + documents.translated("article", "intro", "fr").title
Si tienes un hero-block que debe mostrar un titular obtenido de un documento article:
documents.get("article", "homepage-hero").headline
Resultado: el bloque hero muestra el titular del artículo.
En una página /countries/[code], muestra el nombre completo del país:
documents.get("country", meta.params.code).name
Para /countries/us, meta.params.code es "us" y el resultado es el nombre del país.
meta.locale == "ar-SA" ? "Welcome, everyone" : "Welcome"
documents.get("country", documents.get("article", "us-news").countryCode).name
CEL obtiene primero el artículo, extrae countryCode, obtiene el país y finalmente extrae su nombre.
Si un documento puede no existir, proporciona un valor alternativo:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Artículo no encontrado"
"featured" in documents.get("article", "welcome-post").tags
Devuelve true si el artículo tiene la etiqueta "featured".
size(documents.get("article", "welcome-post").tags)
Devuelve el número de etiquetas.
Las rutas paramétricas son fundamentales para crear páginas dinámicas y localizadas. Al definir un patrón como /{lang}/landingPage, el CMS extrae los parámetros de la URL y los pone a disposición mediante meta.params.
Las rutas usan la sintaxis :paramName o {paramName} para definir segmentos dinámicos:
| Patrón | URL de ejemplo | Parámetros extraídos |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
Cada parámetro puede vincularse a un esquema de documento para validarlo:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
El CMS extrae el segmento, lo valida contra el esquema correspondiente y, si es válido, hace que el documento completo esté disponible en los parámetros resueltos.
meta.segments proporciona la ruta URL sin procesar como un array, útil para acceder a segmentos por posición cuando no hay parámetros con nombre.
| Ruta URL | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
| Caso de uso | Mejor opción |
|---|---|
| Parámetros con nombre del patrón de ruta | meta.params.lang |
| Acceso por posición | meta.segments[0] |
| Obtener la profundidad de la ruta | size(meta.segments) |
| Comprobar si la ruta contiene un segmento | "admin" in meta.segments |
// Obtener el primer segmento
meta.segments[0]
// Comprobar la profundidad
size(meta.segments) > 2 ? "profunda" : "superficial"
// Comprobar si estamos en la sección de administración
"admin" in meta.segments ? "modo administrador" : "modo público"
// Alternativa si el parámetro no está vinculado
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
El objeto meta contiene el contexto de la solicitud actual:
| Propiedad | Tipo | Descripción |
|---|---|---|
meta.locale | string | Código de locale actual, por ejemplo "en-US" o "ar-SA" |
meta.params | Record<string, string> | Parámetros de ruta extraídos de la URL |
meta.segments | string[] | Ruta URL dividida en segmentos |
meta.docId | string \| null | UUID del documento actual, o null para documentos nuevos |
meta.title | string | Título del documento actual |
Los parámetros de ruta siempre son cadenas. Puedes comprobar si existen con has(meta.params.category) y usarlos para obtener documentos:
meta.params.lang
meta.params.country
meta.params.slug
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
meta.docId permite saber si se está editando un documento existente:
meta.docId != null ? "editando" : "creando uno nuevo"
meta.title contiene el título del documento actual:
"Editando: " + meta.title
Cuando el esquema es conocido pero el identificador es dinámico, ref() ofrece una sintaxis más clara:
// Enfoque tradicional
documents.get("airports", meta.params.code).name
// Usando ref()
documents.ref("airports").get(meta.params.code).name
Ambos enfoques son equivalentes.
documents.get("schema", "identifier") // Obtener un documento
documents.get("schema", "id").fieldName // Obtener un campo específico
documents.find("schema") // Obtener todos los documentos
documents.find("schema", { "where": {...}}) // Consulta filtrada
documents.ref("schema").get(identifier) // Consulta encadenada
documents.translated("schema", "id", "fr") // Obtener con locale explícito
meta.locale // "en-US", "ar-SA", etc.
meta.params.xyz // Parámetro de URL llamado "xyz"
meta.segments // Ruta URL como array
meta.segments[0] // Primer segmento
doc.fieldName // Valor de un campo del documento actual
// Comparación
== != < <= > >=
// Lógica
&& || !
// Ternario
condition ? valueIfTrue : valueIfFalse
// Pertenencia
"value" in listOrMap
size(list) // Contar elementos
size(string) // Longitud de una cadena
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // Comprobar si existe una propiedad
hasProperty(obj, "key") // Comprobar si un objeto tiene una clave
| Error | Significado |
|---|---|
SYNTAX_ERROR | Hay un error tipográfico en el script |
TYPE_ERROR | Se están mezclando tipos incompatibles |
RUNTIME_ERROR | El script se ejecutó, pero encontró un problema |
FETCH_LIMIT_EXCEEDED | Se están obteniendo demasiados documentos (máximo 50) |
TIMEOUT | El script tardó demasiado (máximo 5 segundos) |
AST_DEPTH_EXCEEDED | La expresión está demasiado anidada (profundidad máxima: 50) |
SCRIPT_TOO_LONG | El script supera el límite de 5000 caracteres |
El motor CEL está diseñado para ser extensible. Entre las capacidades futuras previstas se incluyen:
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
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"])
Estas capacidades se añadirán mediante el sistema de funciones registradas, manteniendo la compatibilidad con los scripts existentes.
documents. o meta. para ver las opciones disponibles.documents.get("schema", "id") y después añade .fieldName.!= null ? ... : ... cuando un documento pueda no existir.documents.get() o documents.find() cuenta para el límite de 50 consultas.has(meta.params.category) antes de acceder.Las referencias permiten vincular documentos y crear estructuras de contenido relacionales.
Un documento puede referenciar a otro mediante su identificador:
// El artículo guarda el ID del autor y obtiene su nombre
documents.get("author", documents.get("article", "intro").authorId).name
documents.ref("country").get(documents.get("airport", meta.params.code).countryCode).name
También pueden encadenarse varios niveles, por ejemplo, aeropuerto → país → región → continente.
documents.translated("country",
documents.get("airport", meta.params.code).countryCode,
meta.params.lang
).name
Para crear una página de destino multilingüe accesible en /{lang}/landingPage:
greeting con los campos code, headline, subheadline, ctaText y ctaUrl./{lang}/landingPage y vincula lang con el esquema language.documents.get("greeting", meta.params.lang).headline
documents.get("greeting", meta.params.lang).subheadline
documents.get("greeting", meta.params.lang).ctaText
documents.get("greeting", meta.params.lang).ctaUrl
ParametricRoutePage en Next.js para resolver la página, extraer meta.params, evaluar las expresiones CEL en el servidor y renderizar los bloques.Al visitar /ko/landingPage, el CMS coincide con el patrón, extrae meta.params.lang = "ko", valida el parámetro y resuelve el contenido localizado.
interface CelMeta {
locale: string;
params: Record<string, string>;
segments: string[];
docId: string | null;
title: string;
}
Patrón: /{country}/{lang}/products
Ruta: /us/en/products
Algoritmo:
1. Normalizar ambos valores y eliminar las barras finales
2. Dividir en segmentos
3. Comprobar que el número de segmentos coincida
4. Para cada par de segmentos:
- Si el patrón empieza por : o {}, extraerlo como parámetro
- De lo contrario, debe coincidir exactamente
5. Devolver: { country: "us", lang: "en" }
// Vinculación sencilla
{ "lang": "language" }
// Vinculación detallada
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
}
Al usar documents.get(schema, identifier), el CMS busca en este orden:
id.code: comprueba content.code.slug: comprueba content.slug.title.Esto permite hacer referencias flexibles mediante cualquier identificador único.