Um guia prático para escrever expressões CEL no CMS.
Um guia prático para escrever expressões CEL no CMS.
CEL (Common Expression Language) é uma linguagem de script leve integrada ao nosso CMS. Ela permite escrever expressões dinâmicas que podem buscar dados de documentos, ler parâmetros de URL e calcular valores em tempo real.
Veja o que acontece quando um script CEL é executado:
Seu Script O mecanismo Resultado
| | |
v v v
documents.get("article", "intro") --> Busca no banco de dados --> { headline: "Bem-vindo", body: "..." }
.headline --> Extrai o campo --> "Bem-vindo"
Pense no CEL como uma linguagem de consulta somente leitura. Ele não pode modificar nada no banco de dados - apenas lê dados e retorna um resultado calculado. Isso o torna seguro para uso em qualquer lugar do CMS.
Cada expressão CEL tem acesso a três coisas:
| Objeto | O que é | Exemplo |
|---|---|---|
documents | Busca qualquer documento do CMS | documents.get("country", "us") |
meta | Informações sobre a requisição atual (locale, parâmetros de URL) | meta.locale, meta.params.slug |
schema | Definições de campos do documento atual | schema.fields |
O recurso mais poderoso do CEL é buscar documentos de qualquer lugar do seu CMS.
Sintaxe: documents.get(schemaName, identifier)
Suponha que você tenha um documento article armazenado com o identificador "welcome-post":
// Armazenado no CMS como: article / welcome-post
{
"headline": "Bem-vindo à Nossa Plataforma",
"author": "Sarah Chen",
"body": "Estamos empolgados para anunciar...",
"tags": ["anúncio", "notícias"]
}
Para buscar o documento inteiro:
documents.get("article", "welcome-post")
Retorna:
{
"headline": "Bem-vindo à Nossa Plataforma",
"author": "Sarah Chen",
"body": "Estamos empolgados para anunciar...",
"tags": ["anúncio", "notícias"]
}
Para buscar apenas o headline:
documents.get("article", "welcome-post").headline
Retorna: "Bem-vindo à Nossa Plataforma"
Para buscar o autor:
documents.get("article", "welcome-post").author
Retorna: "Sarah Chen"
Quando sua página possui rotas dinâmicas (como /articles/[slug]), você pode usar meta.params para obter o parâmetro da URL e buscar o documento correto.
Se alguém visitar /articles/welcome-post:
documents.get("article", meta.params.slug).headline
Retorna: "Bem-vindo à Nossa Plataforma"
É assim que você constrói páginas dinâmicas - o mesmo script CEL funciona para qualquer artigo, utilizando apenas o slug presente na URL.
Sintaxe: documents.find(schemaName) ou documents.find(schemaName, filter)
// Obter todos os países
documents.find("country")
Retorna:
[
{ "code": "us", "name": "Estados Unidos", "flag": "US" },
{ "code": "sa", "name": "Arábia Saudita", "flag": "SA" },
{ "code": "gb", "name": "Reino Unido", "flag": "GB" }
// Obter países com um filtro
documents.find("country", { "where": { "code": "us" } })
Retorna:
[
{ "code": "us", "name": "Estados Unidos", "flag": "US" }
]
Você tem um hero-block que deve exibir um headline obtido de um documento article.
Seu documento article (identificador: "homepage-hero"):
{
"headline": "Construa Mais Rápido, Entregue com Mais Inteligência",
"subheadline": "O CMS moderno para desenvolvedores"
}
Script CEL no campo de título do bloco hero:
documents.get("article", "homepage-hero").headline
Resultado: O hero exibe "Construa Mais Rápido, Entregue com Mais Inteligência"
Você está construindo uma página em /countries/[code] e quer exibir o nome completo do país.
Seus documentos de país:
// country / us
{ "code": "us", "name": "Estados Unidos", "flag": "US", "languages": ["en", "es"] }
// country / sa
{ "code": "sa", "name": "Arábia Saudita", "flag": "SA", "languages": ["ar", "en"
Script CEL:
documents.get("country", meta.params.code).name
Quando alguém visita /countries/us:
meta.params.code = "us""Estados Unidos"Quando alguém visita /countries/sa:
meta.params.code = "sa""Arábia Saudita"Mostre headlines diferentes com base no locale do usuário.
meta.locale == "ar-SA" ? "مرحبا بكم" : "Bem-vindo"
Se o locale for "ar-SA": Retorna "مرحبا بكم"
Se o locale for qualquer outro: Retorna "Bem-vindo"
Seu article possui um campo countryCode e você quer obter o nome completo do país.
Documento article:
{ "headline": "Notícias dos EUA", "countryCode": "us" }
Script CEL:
documents.get("country", documents.get("article", "us-news").countryCode).name
O que acontece:
documents.get("article", "us-news") retorna { "headline": "Notícias dos EUA", "countryCode": "us" }.countryCode extrai "us"documents.get("country", "us") retorna { "code": "us", "name": "Estados Unidos", ... }.name extrai "Estados Unidos"Resultado: "Estados Unidos"
Se um documento pode não existir, você pode fornecer um valor de fallback:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Artigo não encontrado"
Ou verificar se um campo específico existe:
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "Autor desconhecido"
Seu artigo tem tags e você quer verificar se uma tag específica existe:
"featured" in documents.get("article", "welcome-post").tags
Retorna: true se o artigo tiver a tag "featured"
Obter a primeira tag:
documents.get("article", "welcome-post").tags[0]
Retorna: "anúncio" (a primeira tag)
Contar as tags:
size(documents.get("article", "welcome-post").tags)
Retorna: 2 (número de tags)
Rotas paramétricas são a chave para construir páginas dinâmicas e localizadas. Quando você define um padrão de rota como /{lang}/landingPage, o CMS extrai parâmetros da URL e os disponibiliza via meta.params.
Definição do Padrão de Rota:
Rotas usam a sintaxe :paramName ou {paramName} para definir segmentos dinâmicos:
| Padrão | URL de exemplo | 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" } |
Vinculações de Parâmetros: Cada parâmetro de rota pode ser vinculado a um schema de documento para validação:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
Essa vinculação informa ao CMS:
lang da URLlanguage (procura um documento onde content.code corresponda)Configuração da rota:
/{lang}/landingPage/{lang}/landingPage{ "lang": "language" }Seus documentos de saudação:
// greeting / ko
{ "code": "ko", "headline": "환영합니다", "subheadline": "우리 플랫폼에 오신 것을 환영합니다" }
// greeting / en
{ "code": "en", "headline": "Welcome", "subheadline": "Welcome to our platform" }
// greeting / ja
{ "code": "ja", "headline"
Script CEL para buscar conteúdo localizado:
documents.get("greeting", meta.params.lang).headline
Como isso é resolvido:
| URL | meta.params.lang | Resultado |
|---|---|---|
/ko/landingPage | "ko" | "환영합니다" |
/en/landingPage | "en" | "Welcome" |
/ja/landingPage | "ja" | "ようこそ" |
Para rotas como /{country}/{lang}/products:
Configuração da rota:
{
"pattern": "/{country}/{lang}/products",
"param_bindings": {
"country": "country",
"lang": "language"
}
}
Scripts CEL:
// Obter o nome do país
documents.get("country", meta.params.country).name
// Obter lista de produtos localizada com base no país
documents.find("product", { "where": { "country": meta.params.country } })
// Combinado: Mostrar saudação específica do país no idioma do usuário
documents.get("greeting", meta.params.lang).headline + " de " + documents.get("country", meta.params.country).name
Cascata de validação:
O CMS valida parâmetros hierarquicamente. Para rotas /{country}/{lang}:
country contra o schema countrylang contra o schema languagelang está no array country.languages[] (validação hierárquica)meta.segments fornece o caminho bruto da URL como um array, útil quando você precisa de acesso posicional sem parâmetros nomeados.
Como funciona:
| Caminho da 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 | Melhor abordagem |
|---|---|
| Parâmetros nomeados do padrão de rota | meta.params.lang |
| Acesso baseado em posição | meta.segments[0] |
| Obter profundidade do caminho | size(meta.segments) |
| Verificar se o caminho contém um segmento | "admin" in meta.segments |
// Obter o primeiro segmento (frequentemente o código de idioma)
meta.segments[0]
// Verificar profundidade do caminho
size(meta.segments) > 2 ? "profundo" : "raso"
// Verificar se estamos na seção de administração
"admin" in meta.segments ? "modo admin" : "modo público"
// Fallback: usar o segmento se o parâmetro não estiver vinculado
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
O objeto meta contém todo o contexto sobre a requisição atual:
| Propriedade | Tipo | Descrição | |
|---|---|---|---|
meta.locale | string | Código de locale atual (por exemplo, "en-US", "ko-KR", "ar-SA") | |
meta.params | Record<string, string> | Parâmetros de rota extraídos do padrão de URL | |
meta.segments | string[] | Caminho da URL dividido em segmentos | |
meta.docId | `string \ | null` | UUID do documento atual (nulo para novos documentos) |
meta.title | string | Título do documento atual |
O código de locale segue o formato BCP 47 (idioma-região):
// Verificar locale para idiomas RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// Obter apenas a parte do idioma
meta.locale.split("-")[0] // Não suportado - use meta.params.lang em vez disso
Parâmetros de rota são sempre strings. O CMS os valida contra schemas vinculados antes da avaliação:
// Acessar parâmetro nomeado
meta.params.lang // "ko"
meta.params.country // "us"
meta.params.slug // "welcome-post"
// Verificar se o parâmetro existe
has(meta.params.category) // true/false
// Usar na busca de documentos
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
Segmentos brutos da URL em forma de array:
// Acessar por índice (baseado em 0)
meta.segments[0] // Primeiro segmento
meta.segments[1] // Segundo segmento
// Verificar comprimento
size(meta.segments) // Número de segmentos
// Verificar pertencimento
"products" in meta.segments // O caminho inclui "products"?
O UUID do documento atual, útil para scripts autorreferenciais:
// Disponível apenas ao editar documentos existentes
meta.docId != null ? "editando" : "criando novo"
// Usar em lógica condicional
meta.docId != null ? documents.get("article", meta.docId).status : "rascunho"
O título do documento atual:
// Usar para exibição
"Editando: " + meta.title
// Condicional baseado no título
meta.title.contains("Draft") ? "em andamento" : "publicado"
Para uma sintaxe mais limpa quando o schema é conhecido, mas o identificador é dinâmico:
// Abordagem tradicional
documents.get("airports", meta.params.code).name
// Usando ref() - schema separado do identificador dinâmico
documents.ref("airports").get(meta.params.code).name
Ambas são equivalentes, mas ref() torna a parte dinâmica mais clara.
documents.get("schema", "identifier") // Obter um documento
documents.get("schema", "id").fieldName // Obter um campo específico
documents.find("schema") // Obter todos os documentos
documents.find("schema", { "where": {...}}) // Consulta filtrada
documents.ref("schema").get(identifier) // Busca encadeada
meta.locale // "en-US", "ar-SA", etc.
meta.params.xyz // Parâmetro de URL chamado "xyz"
meta.segments // Caminho da URL como array: ["articles", "intro"]
meta.segments[0] // Primeiro segmento do caminho
meta.docId // ID do documento atual (ou null)
meta.title // Título do documento atual
// Comparação
== != < <= > >=
// Lógica
&& || !
// Ternário (if-else)
condição ? valorSeVerdadeiro : valorSeFalso
// Pertencimento
"value" in listOrMap
size(list) // Contar itens
size(string) // Comprimento de string
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // Verificar se a propriedade existe
Se algo der errado, você verá uma destas mensagens:
| Erro | O que significa |
|---|---|
SYNTAX_ERROR | Erro de digitação no seu script (aspas faltando, operador incorreto) |
TYPE_ERROR | Você está misturando tipos que não funcionam juntos |
RUNTIME_ERROR | O script executou, mas encontrou um problema (variável indefinida) |
FETCH_LIMIT_EXCEEDED | Você está buscando documentos demais (máximo 50) |
TIMEOUT | O script levou tempo demais (máximo 5 segundos) |
AST_DEPTH_EXCEEDED | Expressão profundamente aninhada demais (profundidade máxima: 50) |
SCRIPT_TOO_LONG | O script excede o limite de 5000 caracteres |
O motor CEL foi projetado para extensibilidade. Capacidades futuras planejadas incluem:
// Futuro: Chamar serviços externos via MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// Futuro: Geração de conteúdo com 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"])
Essas capacidades serão adicionadas por meio do sistema de funções registradas, mantendo a retrocompatibilidade com scripts existentes.
documents. ou meta. e o editor mostrará as opções disponíveisdocuments.get("schema", "id"), depois adicione .fieldName!= null ? ... : ...documents.get() ou documents.find() conta para o limite de 50 buscashas(meta.params.category) antes de acessarEste passo a passo cria uma landing page multilíngue acessível em /{lang}/landingPage.
No admin do CMS, crie um schema personalizado chamado greeting:
{
"name": "greeting",
"fields": [
{ "name": "code", "type": "string", "required": true },
{ "name": "headline", "type": "string", "required": true },
{ "name": "subheadline"
Crie documentos para cada idioma:
Documento: greeting / ko
{
"code": "ko",
"headline": "환영합니다",
"subheadline": "우리 플랫폼에 오신 것을 환영합니다",
"ctaText": "시작하기",
"ctaUrl": "/ko/get-started"
}
Documento: greeting / en
{
"code": "en",
"headline": "Welcome",
"subheadline": "Welcome to our platform",
"ctaText": "Get Started",
"ctaUrl": "/en/get-started"
}
Documento: greeting / ja
{
"code": "ja",
"headline": "ようこそ",
"subheadline": "私たちのプラットフォームへようこそ",
"ctaText": "始める",
"ctaUrl": "/ja/get-started"
}
Crie uma rota com a seguinte configuração:
/{lang}/landingPage/{lang}/landingPage {
"lang": "language"
}
Adicione um bloco hero à rota com estes scripts CEL para cada campo:
Campo Headline:
documents.get("greeting", meta.params.lang).headline
Campo Subheadline:
documents.get("greeting", meta.params.lang).subheadline
Campo CTA Text:
documents.get("greeting", meta.params.lang).ctaText
Campo CTA URL:
documents.get("greeting", meta.params.lang).ctaUrl
Crie uma rota catch-all no seu app Next.js:
// app/[...slug]/page.tsx
import { getCmsClient } from '@repo/renderer';
interface PageProps {
params: { slug: string[] };
}
export default async function Page({ params }: PageProps) {
Visite estas URLs para ver o conteúdo localizado:
| URL | Headline esperado |
|---|---|
/ko/landingPage | 환영합니다 |
/en/landingPage | Welcome |
/ja/landingPage | ようこそ |
Quando um usuário visita /ko/landingPage:
/{lang}/landingPagemeta.params.lang = "ko"languagedocuments.get("greeting", meta.params.lang) retornam conteúdo em coreanointerface CelMeta {
/** Código de locale atual (por exemplo, 'en-US') */
locale: string;
/** Parâmetros de rota extraídos da URL */
params: Record<string, string>;
/** Segmentos do caminho de URL */
segments: string[];
/** ID do documento atual (se editando documento existente) */
A função extractParams processa caminhos de URL:
Padrão: /{country}/{lang}/products
Caminho: /us/en/products
Algoritmo:
1. Normalizar ambos (remover barras finais)
2. Dividir em segmentos: ["us", "en", "products"] e ["{country}", "{lang}", "products"]
// Vinculação simples (usa o campo "code" para busca)
{ "lang": "language" }
// Vinculação detalhada (campo slug personalizado)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
Ao buscar via documents.get(schema, identifier):
idcontent.codecontent.slugtitleIsso permite referências flexíveis a documentos por qualquer identificador único.