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 ScaffoldingMedia Library

Sem interface

Guia rápidoSplit Screen JSON Component Builder with LLMComponent Zod Pull

Api rest

Visão geral da 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}postPOST /translationpatchPATCH /translationsgetGET /usagepostPOST /csvpatchPATCH /csv
All Systems Operational
Powered Byprofound-logo
Theme

CEL Scripting in Template Builder

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

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 extraem dados de documentos, leem parâmetros de URL e calculam 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 faz com que seja seguro usá-lo em qualquer lugar do CMS.


Os blocos de construção

Toda expressão CEL tem acesso a três elementos:

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

Auto referência com doc

Ao escrever expressões CEL dentro de um editor de documentos, você pode acessar os valores dos campos do documento atual usando o objeto doc. Isso permite criar campos calculados e referências entre campos.

// Acessar o campo de preço do documento atual
doc.price

// Calcular o total a partir dos campos do documento atual
doc.price * doc.quantity

// Condição baseada no status do documento atual
doc.status == "published" ? doc.title : "Rascunho: " + doc.title

O objeto doc contém todos os valores dos campos do documento editado. Ele é útil para:

  • Campos calculados (por exemplo, doc.price * doc.quantity)
  • Lógica de exibição condicional baseada no estado do documento
  • Expressões no estilo de validação

Buscando documentos

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

Obtendo um único documento

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 felizes em anunciar...",
  "tags": ["announcement", "news"]
}

Para buscar o documento inteiro:

documents.get("article", "welcome-post")

Retorna:

{
  "headline": "Bem-vindo à nossa plataforma",
  "author": "Sarah Chen",
  "body": "Estamos felizes em anunciar...",
  "tags": ["announcement", "news"]
}

Para buscar apenas o título principal:

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 tem 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ê cria páginas dinâmicas: o mesmo script CEL funciona para qualquer artigo, usando o slug presente na URL.


Buscando vários 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" }
]

Traduções

O CEL permite buscar conteúdo traduzido de documentos de duas maneiras: tradução automática baseada na localidade e busca explícita de tradução.

Tradução automática via meta.locale

Quando meta.locale está definido, por exemplo, a partir de parâmetros de rota ou preferências do usuário, documents.get() mescla automaticamente o conteúdo traduzido:

// Se meta.locale for "fr", retorna a tradução francesa mesclada ao documento-base
documents.get("greeting", "welcome").headline

Como funciona:

  1. Busca o conteúdo do documento-base
  2. Se meta.locale não for "en" ou "en-US", procura a tradução na tabela translations
  3. Mescla os campos traduzidos sobre o conteúdo-base: { ...baseContent, ...translatedContent }

Isso significa que os campos traduzidos substituem os campos-base, enquanto os campos sem tradução usam o documento-base.

Tradução explícita com documents.translated()

Para buscar uma tradução específica independentemente da localidade atual:

Sintaxe: documents.translated(schemaName, identifier, locale)

// Sempre buscar a tradução em espanhol
documents.translated("greeting", "welcome", "es").headline

// Buscar a tradução com base em um parâmetro da URL
documents.translated("product", meta.params.id, meta.params.lang).description

// Comparar traduções
documents.translated("article", "intro", "en").title + " / " + documents.translated("article", "intro", "fr").title

Exemplo de tradução

Documentos de saudação com traduções:

// Documento-base: greeting / welcome
{ "headline": "Bem-vindo", "subheadline": "Bem-vindo à nossa plataforma" }

// Tradução (idioma: "fr")
{ "headline": "Bienvenue", "subheadline": "Bienvenue sur notre plateforme" }

// Tradução (idioma: "es")
{ "headline": "Bienvenido", "subheadline": "Bienvenido a nuestra plataforma" }

Scripts CEL:

// Com meta.locale = "fr"
documents.get("greeting", "welcome").headline
// Retorna: "Bienvenue"

// Tradução explícita para espanhol
documents.translated("greeting", "welcome", "es").headline
// Retorna: "Bienvenido"

// Padrão alternativo para traduções ausentes
documents.translated("greeting", "welcome", meta.params.lang) != null
  ? documents.translated("greeting", "welcome", meta.params.lang).headline
  : documents.get("greeting", "welcome").headline

Exemplos do mundo real

Exemplo 1: Título de bloco hero de outro documento

Você tem um hero-block que deve exibir um título principal obtido de um documento article.

Seu documento de artigo (identificador: "homepage-hero"):

{
  "headline": "Crie mais rápido, publique com 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 "Crie mais rápido, publique com inteligência"


Exemplo 2: Nome do país a partir do código

Você está criando uma página em /countries/[code] e quer exibir o nome completo do país.

Seus documentos de países:

// 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 baseado na localidade

Mostre títulos diferentes com base na localidade do usuário.

meta.locale == "ar-SA" ? "Boas-vindas a todos" : "Boas-vindas"

Se a localidade for "ar-SA": retorna "Boas-vindas a todos" Se a localidade for qualquer outra: retorna "Boas-vindas"


Exemplo 4: Buscas encadeadas de documentos

Seu article tem um campo countryCode, e você quer obter o nome completo do país.

Documento do artigo:

{ "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 alternativos

Se um documento talvez não exista, você pode fornecer um valor alternativo:

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"

Rotas paramétricas e meta.params

Rotas paramétricas são essenciais para criar páginas dinâmicas e localizadas. Ao definir um padrão como /{lang}/landingPage, o CMS extrai os parâmetros da URL e os disponibiliza por meio de meta.params.

Como funcionam os parâmetros de rota

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

Cada parâmetro de rota pode ser associado a um schema de documento para validação:

{
  "pattern": "/{lang}/landingPage",
  "param_bindings": {
    "lang": "language"
  }
}

Essa associação informa ao CMS:

  1. Extrair o segmento lang da URL
  2. Validá-lo usando o schema language
  3. Se for válido, disponibilizar o documento completo nos parâmetros resolvidos

Exemplo: página de destino baseada no idioma

Configuração da rota:

  • Caminho: /{lang}/landingPage
  • Padrão: /{lang}/landingPage
  • Associações: { "lang": "language" }

Script CEL para buscar conteúdo localizado:

documents.get("greeting", meta.params.lang).headline

Resolução:

URLmeta.params.langResultado
/ko/landingPage"ko""Bem-vindo"
/en/landingPage"en""Bem-vindo"
/ja/landingPage"ja""Bem-vindo"

Padrão avançado: rotas de país e idioma

Para rotas como /{country}/{lang}/products:

{
  "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 a lista localizada de produtos com base no país
documents.find("product", { "where": { "country": meta.params.country } })

// Combinado: mostrar uma 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

meta.segments — acesso ao caminho bruto da URL

meta.segments fornece o caminho bruto da URL como um array, sendo útil quando você precisa acessar segmentos por posição, sem parâmetros nomeados.

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

Caso de usoMelhor abordagem
Parâmetros nomeados do padrão da rotameta.params.lang
Acesso por posiçãometa.segments[0]
Obter a 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 do idioma)
meta.segments[0]

// Verificar a profundidade do caminho
size(meta.segments) > 2 ? "profundo" : "superficial"

// Verificar se estamos na seção administrativa
"admin" in meta.segments ? "modo administrativo" : "modo público"

// Alternativa: usar o segmento se o parâmetro não estiver associado
has(meta.params.lang) ? meta.params.lang : meta.segments[0]

Referência completa do objeto meta

O objeto meta contém todo o contexto da solicitação atual:

PropriedadeTipoDescrição
meta.localestringCódigo da localidade atual, como "en-US", "ko-KR" ou "ar-SA"
meta.paramsRecord<string, string>Parâmetros de rota extraídos do padrão da URL
meta.segmentsstring[]Caminho da URL dividido em segmentos
meta.docIdstring | nullUUID do documento atual (null para documentos novos)
meta.titlestringTítulo do documento atual

meta.locale

O código de localidade segue o formato BCP 47 (idioma-região):

// Verificar a localidade para idiomas RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"

// Obter somente a parte do idioma
meta.locale.split("-")[0]  // Não suportado — use meta.params.lang

meta.params

Os parâmetros de rota são sempre strings. O CMS valida-os usando os schemas associados antes da avaliação:

// Acessar um parâmetro nomeado
meta.params.lang           // "ko"
meta.params.country        // "us"
meta.params.slug           // "welcome-post"

// Verificar se um parâmetro existe
has(meta.params.category)  // true/false

// Usar em uma busca de documento
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)

meta.docId

O UUID do documento atual, útil para scripts autorreferenciais:

// Disponível somente 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 baseada no título
meta.title.contains("Rascunho") ? "em andamento" : "publicado"

documents.ref() — buscas 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
documents.translated("schema", "id", "fr")  // Obter com localidade explícita

Variáveis de contexto

meta.locale          // "en-US", "ar-SA" etc.
meta.params.xyz      // Parâmetro da URL chamado "xyz"
meta.segments        // Caminho da URL como array
meta.segments[0]     // Primeiro segmento do caminho
meta.docId           // ID do documento atual (ou null)
meta.title           // Título do documento atual
doc.fieldName        // Valor do campo do documento atual (no contexto do editor)

Operadores

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

// Lógica
&&  ||  !

// Ternário (if-else)
condition ? valueIfTrue : valueIfFalse

// Pertencimento
"value" in listOrMap

Funções comuns

size(list)                    // Contar itens
size(string)                  // Comprimento da string
"text".startsWith("te")       // true
"text".endsWith("xt")         // true
"text".contains("ex")         // true
has(object.property)          // Verificar se a propriedade existe
hasProperty(obj, "key")       // Verificar se o objeto possui a chave (sintaxe alternativa)

Mensagens de erro

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

ErroO que significa
SYNTAX_ERRORErro de digitação no script (aspas ausentes ou operador inválido)
TYPE_ERRORTipos incompatíveis estão sendo combinados
RUNTIME_ERRORO script foi executado, mas encontrou um problema (variável indefinida)
FETCH_LIMIT_EXCEEDEDMuitos documentos estão sendo buscados (máximo de 50)
TIMEOUTO script demorou demais (máximo de 5 segundos)
AST_DEPTH_EXCEEDEDA expressão está aninhada profundamente demais (profundidade máxima: 50)
SCRIPT_TOO_LONGO script excede o limite de 5.000 caracteres

Extensibilidade e recursos futuros

O mecanismo CEL foi projetado para ser extensível. Entre os recursos planejados estão:

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: recursos 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"])

Esses recursos serão adicionados por meio do sistema de funções registradas, mantendo a compatibilidade com os scripts existentes.


Dicas

  1. Use o preenchimento automático — digite documents. ou meta. e o editor mostrará as opções disponíveis
  2. Comece de forma simples — teste primeiro documents.get("schema", "id") e depois adicione .fieldName
  3. Verifique valores nulos — se um documento talvez não exista, adicione uma alternativa com != null ? ... : ...
  4. Não busque dados em excesso — 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
  8. Use doc.fieldName para autorreferências — acesse campos do documento atual em expressões calculadas

Referências entre documentos

Esta seção aborda padrões avançados para vincular documentos e criar estruturas de conteúdo relacionais.

Padrão básico de referência

A forma mais simples: um documento referencia outro por identificador.

// O artigo armazena o ID do autor; buscar o nome do autor
documents.get("author", documents.get("article", "intro").authorId).name

Buscas encadeadas com documents.ref()

Para uma sintaxe mais limpa quando o identificador é dinâmico:

// Abordagem tradicional
documents.get("country", documents.get("airport", meta.params.code).countryCode).name

// Usando ref() — mais claro quando o schema é conhecido e o identificador é dinâmico
documents.ref("country").get(documents.get("airport", meta.params.code).countryCode).name

Cadeias de referência multinível

Crie relacionamentos profundos encadeando várias buscas:

// Aeroporto → País → Região → Continente
documents.get("continent",
  documents.get("region",
    documents.get("country",
      documents.get("airport", meta.params.code).countryCode
    ).regionCode
  ).continentCode
).name

Referência com tradução

Combine referências de documentos com traduções:

// Obter o nome localizado do país de um aeroporto
documents.translated("country",
  documents.get("airport", meta.params.code).countryCode,
  meta.params.lang
).name

Boas práticas para referências

  1. Minimize a profundidade das cadeias — cada nível acrescenta latência e aumenta o número de buscas
  2. Armazene resultados intermediários em cache — se precisar do mesmo valor aninhado duas vezes, busque o pai uma única vez
  3. Use verificações de nulo — referências podem falhar se documentos forem excluídos
  4. Prefira códigos a UUIDs — códigos são legíveis nas expressões e estáveis entre ambientes
  5. Observe os limites de busca — cadeias complexas podem atingir rapidamente o limite de 50 buscas

Apêndice A: exemplo completo de rota paramétrica

Este passo a passo cria uma página de destino multilíngue acessível em /{lang}/landingPage.

Etapa 1: criar o schema do documento de saudação

No administrador 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", "type": "string" },
    { "name": "ctaText", "type": "string" },
    { "name": "ctaUrl", "type": "string" }
  ]
}

Etapa 2: criar os documentos de saudação

Crie documentos para cada idioma. Adicione os scripts CEL aos campos correspondentes:

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

Etapa 3: criar a página

Crie uma página com a seguinte configuração:

  • Caminho/Padrão: /{lang}/landingPage
  • Estado: Ativo
  • Mapeamentos de segmentos dinâmicos: mapear lang → o componente language
  {
    "lang": "language"
  }

Etapa 4: adicionar blocos com scripts CEL

Adicione um bloco hero à rota e use os scripts CEL para cada campo.

Etapa 5: consumir no Next.js

Adicione uma rota abrangente. ParametricRoutePage resolve a página, extrai meta.params da URL, avalia seus vínculos CEL no servidor e renderiza cada bloco por meio do registro — você não precisa criar o contexto meta nem chamar o cliente de baixo nível.

// app/[...slug]/page.tsx
import ParametricRoutePage from 'cms-renderer/lib/renderer';
import { registry } from '@/lib/registry';
import { cmsConfig } from '@/lib/cms-config';

export const dynamic = 'force-static';

interface PageProps {
  params: Promise<{ slug: string[] }>;
}

export default async function Page({ params }: PageProps) {
  const { slug } = await params;

  return (
    <ParametricRoutePage
      registry={registry}
      apiKey={cmsConfig.apiKey}
      websiteId={cmsConfig.websiteId}
      cmsUrl={cmsConfig.cmsUrl}
      params={Promise.resolve({ slug })}
    />
  );
}

Etapa 6: testar as rotas

Visite estas URLs para ver o conteúdo localizado:

URLTítulo esperado
/ko/landingPage환영
/en/landingPageBem-vindo
/ja/landingPageいらっしゃいませ

Como funciona a resolução

Quando um usuário visita /ko/landingPage:

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

Apêndice B: referência técnica

Interface CelMeta (TypeScript)

interface CelMeta {
  /** Código da localidade atual (por exemplo, 'en-US') */
  locale: string;
  /** Parâmetros de rota extraídos da URL */
  params: Record<string, string>;
  /** Segmentos do caminho da URL */
  segments: string[];
  /** ID do documento atual (se estiver editando um documento existente) */
  docId: string | null;
  /** Título do documento atual */
  title: string;
}

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"]
3. Comparar a quantidade de segmentos (deve ser igual)
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" }

Formatos de associação de parâmetros compatíveis

// Associação simples (usa o campo "code" para a busca)
{ "lang": "language" }

// Associação detalhada (campo slug personalizado)
{
  "lang": {
    "schemaName": "language",
    "slugField": "code"
  },
  "slug": {
    "schemaName": "article",
    "slugField": "slug"
  }
}

Prioridade da busca de documentos

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

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

Isso permite referências flexíveis a documentos usando qualquer identificador exclusivo.

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