profound-logoProfound CMS
⌘K
Admin
Theme
DocsTutorialBlogPhilosophy
DocsTutorialBlogPhilosophy

Hybrid

Collection of Pages with ComponentsTypes of ComponentsSetup server sent events (SSE) content refetchInstall Profound CMS as a proxyCEL Scripting in Template BuilderProject ScaffoldingBiblioteca multimedia

Sin interfaz

Inicio rápidojson y código de ClaudeComponent Zod Pull

Api rest

Visión general de la API RESTgetConnect your websitegetGET /routesgetGET /routegetGET /blocksgetGET /blocks/with-cel-cachegetGET /blocks/generatedgetGET /componentsgetGET /components/{name}getGET /dataset/{schema_name}getGET /content-changes (SSE)patchPATCH /dataset/{schema_name}postTraducción de publicacionespatchPATCH /translationsgetGET /usagepostPOST /csvpatchPATCH /csv
All Systems Operational
Powered Byprofound-logo
Theme

CEL Scripting in Template Builder

Guía práctica para escribir expresiones CEL en el CMS.

Guía práctica para escribir expresiones CEL en el CMS.


Cómo funciona CEL

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.


Componentes básicos

Cada expresión CEL tiene acceso a tres elementos:

ObjetoQué esEjemplo
documentsObtiene cualquier documento del CMSdocuments.get("country", "us")
metaInformación sobre la solicitud actual (locale y parámetros de URL)meta.locale, meta.params.slug
schemaDefiniciones de campos del documento actualschema.fields

Referencias al documento actual con doc

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

  • Campos calculados (por ejemplo, doc.price * doc.quantity)
  • Lógica de visualización condicional basada en el estado del documento
  • Expresiones de estilo validación

Obtención de documentos

La función más potente de CEL es obtener documentos desde cualquier lugar del CMS.

Obtener un solo documento

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"


Uso de parámetros de URL

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.


Obtener varios documentos

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

Traducciones

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.

Traducción automática mediante meta.locale

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:

  1. Obtiene el contenido del documento base.
  2. Si meta.locale no es "en" ni "en-US", busca la traducción en la tabla translations.
  3. Combina los campos traducidos sobre el contenido base: { ...baseContent, ...translatedContent }.

Los campos traducidos sobrescriben los campos base; los campos sin traducir usan el documento base.

Traducción explícita con documents.translated()

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

Ejemplos prácticos

Ejemplo 1: Título de un bloque hero desde otro documento

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.

Ejemplo 2: Nombre de país a partir de un código

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.

Ejemplo 3: Contenido condicional según el locale

meta.locale == "ar-SA" ? "Welcome, everyone" : "Welcome"

Ejemplo 4: Consultas encadenadas

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.

Ejemplo 5: Valores alternativos

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"

Ejemplo 6: Trabajo con listas

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


Rutas paramétricas y meta.params

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.

Cómo funcionan los parámetros de ruta

Las rutas usan la sintaxis :paramName o {paramName} para definir segmentos dinámicos:

PatrónURL de ejemploPará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: acceso a la ruta URL sin procesar

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

Cuándo usar meta.segments o meta.params

Caso de usoMejor opción
Parámetros con nombre del patrón de rutameta.params.lang
Acceso por posiciónmeta.segments[0]
Obtener la profundidad de la rutasize(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]

Referencia completa del objeto meta

El objeto meta contiene el contexto de la solicitud actual:

PropiedadTipoDescripción
meta.localestringCódigo de locale actual, por ejemplo "en-US" o "ar-SA"
meta.paramsRecord<string, string>Parámetros de ruta extraídos de la URL
meta.segmentsstring[]Ruta URL dividida en segmentos
meta.docIdstring \| nullUUID del documento actual, o null para documentos nuevos
meta.titlestringTí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

documents.ref(): consultas encadenadas

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.


Referencia rápida

Obtención de documentos

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

Variables de contexto

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

Operadores

// Comparación
==  !=  <  <=  >  >=

// Lógica
&&  ||  !

// Ternario
condition ? valueIfTrue : valueIfFalse

// Pertenencia
"value" in listOrMap

Funciones comunes

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

Mensajes de error

ErrorSignificado
SYNTAX_ERRORHay un error tipográfico en el script
TYPE_ERRORSe están mezclando tipos incompatibles
RUNTIME_ERROREl script se ejecutó, pero encontró un problema
FETCH_LIMIT_EXCEEDEDSe están obteniendo demasiados documentos (máximo 50)
TIMEOUTEl script tardó demasiado (máximo 5 segundos)
AST_DEPTH_EXCEEDEDLa expresión está demasiado anidada (profundidad máxima: 50)
SCRIPT_TOO_LONGEl script supera el límite de 5000 caracteres

Extensibilidad y capacidades futuras

El motor CEL está diseñado para ser extensible. Entre las capacidades futuras previstas se incluyen:

Previsto: integración con servidores MCP

mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)

Previsto: capacidades de 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"])

Estas capacidades se añadirán mediante el sistema de funciones registradas, manteniendo la compatibilidad con los scripts existentes.


Consejos

  1. Usa el autocompletado: escribe documents. o meta. para ver las opciones disponibles.
  2. Empieza de forma sencilla: prueba primero documents.get("schema", "id") y después añade .fieldName.
  3. Comprueba los valores nulos: usa != null ? ... : ... cuando un documento pueda no existir.
  4. No obtengas datos innecesarios: cada documents.get() o documents.find() cuenta para el límite de 50 consultas.
  5. Prefiere meta.params a meta.segments: los parámetros con nombre se validan y son más fiables.
  6. Usa has() para parámetros opcionales: comprueba has(meta.params.category) antes de acceder.
  7. Usa documents.ref() para identificadores dinámicos: la sintaxis es más clara cuando el esquema es fijo.
  8. Usa doc.fieldName para autorreferencias: accede a los campos del documento actual en expresiones calculadas.

Referencias entre documentos

Las referencias permiten vincular documentos y crear estructuras de contenido relacionales.

Patrón básico de referencia

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

Cadenas de referencias

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.

Referencias con traducción

documents.translated("country",
  documents.get("airport", meta.params.code).countryCode,
  meta.params.lang
).name

Buenas prácticas

  1. Minimiza la profundidad de las cadenas: cada nivel aumenta la latencia y el número de consultas.
  2. Almacena resultados intermedios cuando necesites dos veces el mismo valor anidado.
  3. Usa comprobaciones de null: las referencias pueden fallar si se eliminan documentos.
  4. Prefiere códigos sobre UUID: son más legibles y estables entre entornos.
  5. Vigila los límites de consultas: las cadenas complejas pueden alcanzar rápidamente el límite de 50.

Apéndice: ejemplo completo de una ruta paramétrica

Para crear una página de destino multilingüe accesible en /{lang}/landingPage:

  1. Crea un esquema greeting con los campos code, headline, subheadline, ctaText y ctaUrl.
  2. Crea un documento de saludo para cada idioma.
  3. Configura la ruta /{lang}/landingPage y vincula lang con el esquema language.
  4. Añade bloques con expresiones como:
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
  1. Usa 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.


Apéndice: referencia técnica

Interfaz CelMeta (TypeScript)

interface CelMeta {
  locale: string;
  params: Record<string, string>;
  segments: string[];
  docId: string | null;
  title: string;
}

Algoritmo de extracción de parámetros

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

Formatos de vinculación compatibles

// Vinculación sencilla
{ "lang": "language" }

// Vinculación detallada
{
  "lang": {
    "schemaName": "language",
    "slugField": "code"
  },
  "slug": {
    "schemaName": "article",
    "slugField": "slug"
  }
}

Prioridad al obtener documentos

Al usar documents.get(schema, identifier), el CMS busca en este orden:

  1. Coincidencia de UUID: si el identificador es un UUID válido, busca por id.
  2. Campo code: comprueba content.code.
  3. Campo slug: comprueba content.slug.
  4. Coincidencia de título: comprueba el campo title.

Esto permite hacer referencias flexibles mediante cualquier identificador único.

Continue Reading
Previous‹Install Profound CMS as a proxyNextProject Scaffolding›