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

Scripting no Construtor de Templates

Um guia prático para escrever expressões CEL no CMS.

Continue Reading
Previous‹Renderização estática com suporte ao modo de ediçãoNextCreate Profound Next›

Híbrido

Projeto RenderizadorRoteamento ParamétricoTipos De ComponentesSseConfigurar Proxy Do Painel AdministrativoRenderização estática com suporte ao modo de ediçãoScripting no Construtor de TemplatesCreate Profound Next

Sem interface

Início rápidoJson E Claude CodeComponente Zod Puxar

Mcp

Mcp

Recursos Do Cms

Recurso Modelo De DocumentosRecurso Construtor De ModelosTradutor De FuncionalidadesRecurso Organizacao

Motivação

Nossa Abordagem

Terminologia

Hibrido Vs Headless

Um guia prático para escrever expressões CEL no CMS.


Como o CEL Funciona

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.


Os Blocos Fundamentais

Cada expressão CEL tem acesso a três coisas:

ObjetoO que éExemplo
documentsBusca qualquer documento do CMSdocuments.get("country", "us")
metaInformações sobre a requisição atual (locale, parâmetros de URL)meta.locale, meta.params.slug
schemaDefinições de campos do documento atualschema.fields

Buscando Documentos

O recurso mais poderoso do CEL é buscar documentos de qualquer lugar do seu CMS.

Obtendo um Documento Único

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"


Usando Parâmetros de URL

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.


Buscando Múltiplos Documentos

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

Exemplos do Mundo Real

Exemplo 1: Título do Bloco Hero vindo de Outro Documento

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"


Exemplo 2: Nome do País a partir do Código

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"
  • Resultado: "Estados Unidos"

Quando alguém visita /countries/sa:

  • meta.params.code = "sa"
  • Resultado: "Arábia Saudita"

Exemplo 3: Conteúdo Condicional com base no Locale

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"


Exemplo 4: Pesquisas de Documentos Encadeadas

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:

  1. documents.get("article", "us-news") retorna { "headline": "Notícias dos EUA", "countryCode": "us" }
  2. .countryCode extrai "us"
  3. documents.get("country", "us") retorna { "code": "us", "name": "Estados Unidos", ... }
  4. .name extrai "Estados Unidos"

Resultado: "Estados Unidos"


Exemplo 5: Valores de Fallback

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"

Exemplo 6: Trabalhando com Listas

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 e meta.params

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.

Como Funcionam os Parâmetros de Rota

Definição do Padrão de Rota: Rotas usam a sintaxe :paramName ou {paramName} para definir segmentos dinâmicos:

PadrãoURL de exemploParâ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:

  1. Extrair o segmento lang da URL
  2. Validá-lo com o schema language (procura um documento onde content.code corresponda)
  3. Se válido, disponibilizar o documento completo nos parâmetros resolvidos

Exemplo: Landing Page baseada em Idioma

Configuração da rota:

  • Caminho: /{lang}/landingPage
  • Padrão: /{lang}/landingPage
  • Vinculações de parâmetros: { "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:

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

Padrão Avançado: Rotas de País + Idioma

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

  1. Valida o parâmetro country contra o schema country
  2. Valida o parâmetro lang contra o schema language
  3. Opcionalmente valida se lang está no array country.languages[] (validação hierárquica)

meta.segments - Acesso bruto ao caminho da URL

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

Quando usar meta.segments vs meta.params

Caso de usoMelhor abordagem
Parâmetros nomeados do padrão de rotameta.params.lang
Acesso baseado em posiçãometa.segments[0]
Obter profundidade do caminhosize(meta.segments)
Verificar se o caminho contém um segmento"admin" in meta.segments

Exemplos com 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]

Referência Completa do Objeto meta

O objeto meta contém todo o contexto sobre a requisição atual:

PropriedadeTipoDescrição
meta.localestringCódigo de locale atual (por exemplo, "en-US", "ko-KR", "ar-SA")
meta.paramsRecord<string, string>Parâmetros de rota extraídos do padrão de URL
meta.segmentsstring[]Caminho da URL dividido em segmentos
meta.docId`string \null`UUID do documento atual (nulo para novos documentos)
meta.titlestringTítulo do documento atual

meta.locale

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

meta.params

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)

meta.segments

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

meta.docId

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"

meta.title

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"

documents.ref() - Pesquisas Encadeadas

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.


Referência Rápida

Busca de Documentos

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

Variáveis de Contexto

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

Operadores

// Comparação
==  !=  <  <=  >  >=

// Lógica
&&  ||  !

// Ternário (if-else)
condição ? valorSeVerdadeiro : valorSeFalso

// Pertencimento
"value" in listOrMap

Funções Comuns

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

Mensagens de Erro

Se algo der errado, você verá uma destas mensagens:

ErroO que significa
SYNTAX_ERRORErro de digitação no seu script (aspas faltando, operador incorreto)
TYPE_ERRORVocê está misturando tipos que não funcionam juntos
RUNTIME_ERRORO script executou, mas encontrou um problema (variável indefinida)
FETCH_LIMIT_EXCEEDEDVocê está buscando documentos demais (máximo 50)
TIMEOUTO script levou tempo demais (máximo 5 segundos)
AST_DEPTH_EXCEEDEDExpressão profundamente aninhada demais (profundidade máxima: 50)
SCRIPT_TOO_LONGO script excede o limite de 5000 caracteres

Extensibilidade e Capacidades Futuras

O motor CEL foi projetado para extensibilidade. Capacidades futuras planejadas incluem:

Planejado: Integração com Servidor MCP

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

Planejado: Capacidades de IA

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


Dicas

  1. Use o autocomplete - Digite documents. ou meta. e o editor mostrará as opções disponíveis
  2. Comece simples - Teste primeiro com documents.get("schema", "id"), depois adicione .fieldName
  3. Verifique null - Se um documento pode não existir, adicione um fallback com != null ? ... : ...
  4. Evite buscar demais - Cada documents.get() ou documents.find() conta para o limite de 50 buscas
  5. Prefira meta.params a meta.segments - Parâmetros nomeados são validados e mais confiáveis
  6. Use has() para parâmetros opcionais - Verifique has(meta.params.category) antes de acessar
  7. Use documents.ref() para identificadores dinâmicos - Sintaxe mais clara quando o schema é estático, mas o identificador é dinâmico

Apêndice A: Exemplo Completo de Rota Paramétrica

Este passo a passo cria uma landing page multilíngue acessível em /{lang}/landingPage.

Passo 1: Criar o Schema de Documento Greeting

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"

Passo 2: Criar Documentos Greeting

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

Passo 3: Criar a Rota

Crie uma rota com a seguinte configuração:

  • Caminho: /{lang}/landingPage
  • Padrão: /{lang}/landingPage
  • Estado: Live
  • Vinculações de Parâmetros:
  {
    "lang": "language"
  }

Passo 4: Adicionar Blocos com Scripts CEL

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

Passo 5: Consumir no Next.js

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

Passo 6: Testar as Rotas

Visite estas URLs para ver o conteúdo localizado:

URLHeadline esperado
/ko/landingPage환영합니다
/en/landingPageWelcome
/ja/landingPageようこそ

Como a Resolução Funciona

Quando um usuário visita /ko/landingPage:

  1. Correspondência de rota: o CMS corresponde ao padrão /{lang}/landingPage
  2. Extração de parâmetros: meta.params.lang = "ko"
  3. Validação: o CMS valida se "ko" existe no schema language
  4. Avaliação CEL: scripts como documents.get("greeting", meta.params.lang) retornam conteúdo em coreano
  5. Resposta: blocos localizados são retornados ao cliente

Apêndice B: Referência Técnica

Interface CelMeta (TypeScript)

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

Algoritmo de Extração de Parâmetros

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

Formatos de Vinculação de Parâmetros Suportados

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

Prioridade de Busca de Documentos

Ao buscar via documents.get(schema, identifier):

  1. Correspondência de UUID: se o identificador for um UUID válido, busca por id
  2. Campo code: verifica o campo content.code
  3. Campo slug: verifica o campo content.slug
  4. Correspondência de título: verifica o campo title

Isso permite referências flexíveis a documentos por qualquer identificador único.

]
] }
:
"ようこそ"
,
"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('/');
// Obter rota e parâmetros resolvidos
const { route, resolvedParams } = await client.routes.getRouteByPath.query({
websiteId: process.env.CMS_WEBSITE_ID!,
path,
});
// Obter blocos da rota
const blocks = await client.blocks.getBlocks.query({
websiteId: process.env.CMS_WEBSITE_ID!,
blockIds: route.block_ids,
// Passar parâmetros resolvidos para o contexto do 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: {},
},
});
// Renderizar blocos
return (
<main>
{blocks.map((block) => (
<BlockRenderer
key={block.id}
block={block}
routeParams={resolvedParams}
language={resolvedParams?.lang?.value}
/>
))}
</main>
);
}
docId: string | null;
/** Título do documento atual */
title: string;
}
3. Conferir contagem de segmentos (devem ser iguais)
4. Para cada par de segmentos:
- Se o padrão começar com : ou {}, extrair como parâmetro
- Caso contrário, deve corresponder exatamente
5. Retornar: { country: "us", lang: "en" }
}