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

Scripting Nel Template Builder

Una guida pratica alla scrittura di espressioni CEL nel CMS.

Continue Reading
Previous‹Rendering Statico Con Supporto Della Modalita Di ModificaNextCreate Profound Next›

Ibrido

Progetto RendererInstradamento parametricoTipi Di ComponentiSseConfigura Proxy Pannello Di AmministrazioneRendering Statico Con Supporto Della Modalita Di ModificaScripting Nel Template BuilderCreate Profound Next

Senza testa

Avvio rapidoJson And Claude CodeComponent Zod Pull

Mcp

Mcp

funzionalità CMS

Feat Modello DocumentazioneFunzionalità Generatore Di ModelliFeat TraduttoreFeat Organizzazione

Motivazione

Il nostro approccio

Terminologia

Ibrido vs Headless

Una guida pratica alla scrittura di espressioni CEL nel CMS.


Come funziona CEL

CEL (Common Expression Language) è un linguaggio di scripting leggero integrato nel nostro CMS. Ti permette di scrivere espressioni dinamiche che possono recuperare dati dai documenti, leggere i parametri dell'URL e calcolare valori al volo.

Ecco che cosa succede quando viene eseguito uno script CEL:

Il tuo script                    Il motore                     Risultato
    |                              |                              |
    v                              v                              v
documents.get("article", "intro") --> Recupera dal database --> { headline: "Benvenuto", body: "..." }
         .headline                --> Estrae il campo          --> "Benvenuto"

Considera CEL come un linguaggio di query in sola lettura. Non può modificare nulla nel database: si limita a leggere i dati e restituire un risultato calcolato. Questo lo rende sicuro da utilizzare ovunque nel CMS.


I blocchi fondamentali

Ogni espressione CEL ha accesso a tre elementi:

OggettoChe cos'èEsempio
documentsRecupera qualsiasi documento dal CMSdocuments.get("country", "us")
metaInformazioni sulla richiesta corrente (locale, parametri URL)meta.locale, meta.params.slug
schemaLe definizioni dei campi del documento correnteschema.fields

Recuperare documenti

La funzione più potente di CEL è il recupero di documenti da qualsiasi punto del tuo CMS.

Ottenere un singolo documento

Sintassi: documents.get(schemaName, identifier)

Supponiamo che tu abbia un documento article archiviato con l'identificatore "welcome-post":

// Archiviato nel CMS come: article / welcome-post
{
  "headline": "Benvenuti sulla nostra piattaforma",
  "author": "Sarah Chen",
  "body": "Siamo entusiasti di annunciare...",
  "tags": ["annuncio", "notizie"]
}

Per recuperare l'intero documento:

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

Restituisce:

{
  "headline": "Benvenuti sulla nostra piattaforma",
  "author": "Sarah Chen",
  "body": "Siamo entusiasti di annunciare...",
  "tags": ["annuncio", "notizie"]
}

Per recuperare solo il titolo:

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

Restituisce: "Benvenuti sulla nostra piattaforma"

Per recuperare l'autore:

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

Restituisce: "Sarah Chen"


Utilizzare i parametri URL

Quando la tua pagina ha route dinamiche (come /articles/[slug]), puoi usare meta.params per ottenere il parametro dell'URL e recuperare il documento corretto.

Se qualcuno visita /articles/welcome-post:

documents.get("article", meta.params.slug).headline

Restituisce: "Benvenuti sulla nostra piattaforma"

In questo modo crei pagine dinamiche: lo stesso script CEL funziona per qualsiasi articolo, utilizzando semplicemente lo slug presente nell'URL.


Recuperare più documenti

Sintassi: documents.find(schemaName) oppure documents.find(schemaName, filter)

// Recupera tutti i paesi
documents.find("country")

Restituisce:

[
  { "code": "us", "name": "Stati Uniti", "flag": "US" },
  { "code": "sa", "name": "Arabia Saudita", "flag": "SA" },
  { "code": "gb", "name": "Regno Unito", "flag": "GB" }

// Recupera i paesi con un filtro
documents.find("country", { "where": { "code": "us" } })

Restituisce:

[
  { "code": "us", "name": "Stati Uniti", "flag": "US" }
]

Esempi reali

Esempio 1: Titolo dell'hero block da un altro documento

Hai un hero-block che dovrebbe mostrare un titolo preso da un documento article.

Il tuo documento article (identificatore: "homepage-hero"):

{
  "headline": "Sviluppa più velocemente, distribuisci in modo più intelligente",
  "subheadline": "Il CMS moderno per gli sviluppatori"
}

Script CEL nel campo titolo dell'hero block:

documents.get("article", "homepage-hero").headline

Risultato: l'hero mostra "Sviluppa più velocemente, distribuisci in modo più intelligente"


Esempio 2: Nome del paese dal codice

Stai creando una pagina in /countries/[code] e vuoi mostrare il nome completo del paese.

I tuoi documenti country:

// country / us
{ "code": "us", "name": "Stati Uniti", "flag": "US", "languages": ["en", "es"] }

// country / sa
{ "code": "sa", "name": "Arabia Saudita", "flag": "SA", "languages": ["ar", "en"

Script CEL:

documents.get("country", meta.params.code).name

Quando qualcuno visita /countries/us:

  • meta.params.code = "us"
  • Risultato: "Stati Uniti"

Quando qualcuno visita /countries/sa:

  • meta.params.code = "sa"
  • Risultato: "Arabia Saudita"

Esempio 3: Contenuto condizionale in base al locale

Mostra titoli diversi in base al locale dell'utente.

meta.locale == "ar-SA" ? "مرحبا بكم" : "Benvenuto"

Se il locale è "ar-SA": restituisce "مرحبا بكم" Se il locale è qualsiasi altro: restituisce "Benvenuto"


Esempio 4: Ricerca di documenti concatenata

Il tuo article ha un campo countryCode e vuoi ottenere il nome completo del paese.

Documento article:

{ "headline": "Notizie dagli Stati Uniti", "countryCode": "us" }

Script CEL:

documents.get("country", documents.get("article", "us-news").countryCode).name

Cosa succede:

  1. documents.get("article", "us-news") restituisce { "headline": "Notizie dagli Stati Uniti", "countryCode": "us" }
  2. .countryCode estrae "us"
  3. documents.get("country", "us") restituisce { "code": "us", "name": "Stati Uniti", ... }
  4. .name estrae "Stati Uniti"

Risultato: "Stati Uniti"


Esempio 5: Valori di fallback

Se un documento potrebbe non esistere, puoi fornire un fallback:

documents.get("article", meta.params.slug) != null
  ? documents.get("article", meta.params.slug).headline
  : "Articolo non trovato"

Oppure verifica se esiste un campo specifico:

documents.get("article", "intro").author != null
  ? documents.get("article", "intro").author
  : "Autore sconosciuto"

Esempio 6: Lavorare con le liste

Il tuo articolo ha dei tag e vuoi verificare se esiste un tag specifico:

"in primo piano" in documents.get("article", "welcome-post").tags

Restituisce: true se l'articolo ha il tag "in primo piano"

Ottieni il primo tag:

documents.get("article", "welcome-post").tags[0]

Restituisce: "annuncio" (il primo tag)

Conta i tag:

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

Restituisce: 2 (numero di tag)


Route parametriche e meta.params

Le route parametriche sono la chiave per creare pagine dinamiche e localizzate. Quando definisci un pattern di route come /{lang}/landingPage, il CMS estrae i parametri dall'URL e li rende disponibili tramite meta.params.

Come funzionano i parametri di route

Definizione del pattern di route: Le route usano la sintassi :paramName o {paramName} per definire segmenti dinamici:

PatternURL di esempioParametri estratti
/:lang/landingPage/ko/landingPage{ lang: "ko" }
/{country}/{lang}/products/us/en/products{ country: "us", lang: "en" }
/articles/:slug/articles/welcome-post{ slug: "welcome-post" }

Associazioni dei parametri: Ogni parametro di route può essere associato a uno schema di documento per la convalida:

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

Questa associazione indica al CMS:

  1. Estrai il segmento lang dall'URL
  2. Convalidalo rispetto allo schema language (cerca un documento in cui content.code corrisponde)
  3. Se valido, rendi disponibile l'intero documento nei parametri risolti

Esempio: landing page basata sulla lingua

Configurazione della route:

  • Percorso: /{lang}/landingPage
  • Pattern: /{lang}/landingPage
  • Associazioni dei parametri: { "lang": "language" }

I tuoi documenti greeting:

// greeting / ko
{ "code": "ko", "headline": "환영합니다", "subheadline": "우리 플랫폼에 오신 것을 환영합니다" }

// greeting / en
{ "code": "en", "headline": "Benvenuto", "subheadline": "Benvenuto sulla nostra piattaforma" }

// greeting / ja
{ "code": "ja", 

Script CEL per recuperare il contenuto localizzato:

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

Come si risolve:

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

Pattern avanzato: route Paese + lingua

Per route come /{country}/{lang}/products:

Configurazione della route:

{
  "pattern": "/{country}/{lang}/products",
  "param_bindings": {
    "country": "country",
    "lang": "language"
  }
}

Script CEL:

// Ottieni il nome del paese
documents.get("country", meta.params.country).name

// Ottieni l'elenco di prodotti localizzato in base al paese
documents.find("product", { "where": { "country": meta.params.country } })

// Combinato: mostra un saluto specifico del paese nella lingua dell'utente
documents.get("greeting", meta.params.lang).headline + " da " + documents.get("country", meta.params.country).name

Cascata di convalida: Il CMS convalida i parametri in modo gerarchico. Per le route /{country}/{lang}:

  1. Convalida il parametro country rispetto allo schema country
  2. Convalida il parametro lang rispetto allo schema language
  3. Facoltativamente, verifica che lang sia presente nell'array country.languages[] (convalida gerarchica)

meta.segments - accesso al percorso URL grezzo

meta.segments fornisce il percorso URL grezzo come array, utile quando hai bisogno di un accesso posizionale senza parametri nominati.

Come funziona:

Percorso URLmeta.segments
/articles/tech/ai-news["articles", "tech", "ai-news"]
/ko/landingPage["ko", "landingPage"]
/us/en/products/featured["us", "en", "products", "featured"]
/[]

Quando usare meta.segments vs meta.params

Caso d'usoApproccio migliore
Parametri nominati dal pattern della routemeta.params.lang
Accesso basato sulla posizionemeta.segments[0]
Ottenere la profondità del percorsosize(meta.segments)
Verificare se il percorso contiene un segmento"admin" in meta.segments

Esempi con meta.segments

// Ottieni il primo segmento (spesso il codice della lingua)
meta.segments[0]

// Controlla la profondità del percorso
size(meta.segments) > 2 ? "profondo" : "superficiale"

// Verifica se siamo nella sezione admin
"admin" in meta.segments ? "modalità admin" : "modalità pubblica"

// Fallback: usa il segmento se il parametro non è associato
has(meta.params.lang) ? meta.params.lang : meta.segments[0]

Riferimento completo all'oggetto meta

L'oggetto meta contiene tutto il contesto sulla richiesta corrente:

ProprietàTipoDescrizione
meta.localestringCodice locale corrente (ad es. "en-US", "ko-KR", "ar-SA")
meta.paramsRecord<string, string>Parametri di route estratti dal pattern URL
meta.segmentsstring[]Percorso URL suddiviso in segmenti
meta.docId`string \null`UUID del documento corrente (null per i nuovi documenti)
meta.titlestringTitolo del documento corrente

meta.locale

Il codice locale segue il formato BCP 47 (lingua-regione):

// Controlla se il locale è RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"

// Ottieni solo la parte della lingua
meta.locale.split("-")[0]  // Non supportato - usa invece meta.params.lang

meta.params

I parametri di route sono sempre stringhe. Il CMS li convalida rispetto agli schemi associati prima dell'esecuzione:

// Accedi al parametro nominale
meta.params.lang           // "ko"
meta.params.country        // "us"
meta.params.slug           // "welcome-post"

// Verifica se il parametro esiste
has(meta.params.category)  // true/false

// Usa nel recupero dei documenti
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)

meta.segments

Segmenti grezzi dell'URL come array:

// Accedi tramite indice (partendo da 0)
meta.segments[0]           // Primo segmento
meta.segments[1]           // Secondo segmento

// Verifica la lunghezza
size(meta.segments)        // Numero di segmenti

// Verifica la presenza
"products" in meta.segments  // Il percorso include "products"?

meta.docId

L'UUID del documento corrente, utile per script autoreferenziali:

// Disponibile solo durante la modifica di documenti esistenti
meta.docId != null ? "modifica" : "creazione"

// Usa nella logica condizionale
meta.docId != null ? documents.get("article", meta.docId).status : "bozza"

meta.title

Il titolo del documento corrente:

// Usa per la visualizzazione
"Modifica: " + meta.title

// Condizione basata sul titolo
meta.title.contains("Bozza") ? "in lavorazione" : "pubblicato"

documents.ref() - ricerche concatenate

Per una sintassi più pulita quando lo schema è noto ma l'identificatore è dinamico:

// Approccio tradizionale
documents.get("airports", meta.params.code).name

// Uso di ref() - schema separato dall'identificatore dinamico
documents.ref("airports").get(meta.params.code).name

Entrambi sono equivalenti, ma ref() rende più chiara la parte dinamica.


Riepilogo rapido

Recupero dei documenti

documents.get("schema", "identifier")       // Recupera un documento
documents.get("schema", "id").fieldName     // Recupera un campo specifico
documents.find("schema")                    // Recupera tutti i documenti
documents.find("schema", { "where": {...}}) // Query filtrata
documents.ref("schema").get(identifier)     // Ricerca concatenata

Variabili di contesto

meta.locale          // "en-US", "ar-SA", ecc.
meta.params.xyz      // Parametro URL chiamato "xyz"
meta.segments        // Percorso URL come array: ["articles", "intro"]
meta.segments[0]     // Primo segmento del percorso
meta.docId           // ID del documento corrente (oppure null)
meta.title           // Titolo del documento corrente

Operatori

// Confronto
==  !=  <  <=  >  >=

// Logica
&&  ||  !

// Operatore ternario (if-else)
condition ? valueIfTrue : valueIfFalse

// Appartenenza
"value" in listOrMap

Funzioni comuni

size(list)                    // Conta gli elementi
size(string)                  // Lunghezza della stringa
"testo".startsWith("te")       // true
"testo".endsWith("to")         // true
"testo".contains("es")         // true
has(object.property)          // Verifica se la proprietà esiste

Messaggi di errore

Se qualcosa va storto, vedrai uno di questi messaggi:

ErroreCosa significa
SYNTAX_ERRORC'è un refuso nello script (virgolette mancanti, operatore errato)
TYPE_ERRORStai mescolando tipi che non funzionano insieme
RUNTIME_ERRORLo script è stato eseguito ma ha incontrato un problema (variabile non definita)
FETCH_LIMIT_EXCEEDEDStai recuperando troppi documenti (massimo 50)
TIMEOUTLo script ha impiegato troppo tempo (massimo 5 secondi)
AST_DEPTH_EXCEEDEDEspressione troppo annidata (profondità massima: 50)
SCRIPT_TOO_LONGLo script supera il limite di 5000 caratteri

Estensibilità e funzionalità future

Il motore CEL è progettato per l'estensibilità. Le funzionalità future previste includono:

Pianificato: integrazione con server MCP

// Futuro: chiamare servizi esterni tramite MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)

Pianificato: funzionalità AI

// Futuro: generazione di contenuti basata sull'AI
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"])

Queste funzionalità verranno aggiunte tramite il sistema di funzioni registrate, mantenendo la retrocompatibilità con gli script esistenti.


Suggerimenti

  1. Usa l'autocompletamento - Digita documents. o meta. e l'editor mostrerà le opzioni disponibili
  2. Inizia in modo semplice - Prova prima con documents.get("schema", "id"), poi aggiungi .fieldName
  3. Controlla i null - Se un documento potrebbe non esistere, aggiungi un fallback con != null ? ... : ...
  4. Non eccedere con i recuperi - Ogni documents.get() o documents.find() conta ai fini del limite di 50 recuperi
  5. Preferisci meta.params a meta.segments - I parametri nominati sono convalidati e più affidabili
  6. Usa has() per i parametri opzionali - Verifica has(meta.params.category) prima di accedere
  7. Usa documents.ref() per identificatori dinamici - Sintassi più chiara quando lo schema è statico ma l'identificatore è dinamico

Appendice A: esempio completo di route parametrica

Questo walkthrough crea una landing page multilingue accessibile all'indirizzo /{lang}/landingPage.

Passo 1: crea lo schema del documento greeting

Nel pannello di amministrazione del CMS, crea uno schema personalizzato chiamato greeting:

{
  "name": "greeting",
  "fields": [
    { "name": "code", "type": "string", "required": true },
    { "name": "headline", "type": "string", "required": true },
    { "name": "subheadline"

Passo 2: crea i documenti greeting

Crea documenti per ogni lingua:

Documento: greeting / ko

{
  "code": "ko",
  "headline": "환영합니다",
  "subheadline": "우리 플랫폼에 오신 것을 환영합니다",
  "ctaText": "시작하기",
  "ctaUrl": "/ko/get-started"
}

Documento: greeting / en

{
  "code": "en",
  "headline": "Benvenuto",
  "subheadline": "Benvenuto sulla nostra piattaforma",
  "ctaText": "Inizia",
  "ctaUrl": "/en/get-started"
}

Documento: greeting / ja

{
  "code": "ja",
  "headline": "ようこそ",
  "subheadline": "私たちのプラットフォームへようこそ",
  "ctaText": "始める",
  "ctaUrl": "/ja/get-started"
}

Passo 3: crea la route

Crea una route con la seguente configurazione:

  • Percorso: /{lang}/landingPage
  • Pattern: /{lang}/landingPage
  • Stato: Live
  • Associazioni dei parametri:
  {
    "lang": "language"
  }

Passo 4: aggiungi blocchi con script CEL

Aggiungi un hero block alla route con questi script CEL per ciascun campo:

Campo titolo:

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

Campo sottotitolo:

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

Campo testo CTA:

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

Campo URL CTA:

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

Passo 5: usa in Next.js

Crea una route catch-all nella tua 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: testa le route

Visita questi URL per vedere il contenuto localizzato:

URLTitolo atteso
/ko/landingPage환영합니다
/en/landingPageBenvenuto
/ja/landingPageようこそ

Come funziona la risoluzione

Quando un utente visita /ko/landingPage:

  1. Corrispondenza della route: il CMS abbina il pattern /{lang}/landingPage
  2. Estrazione dei parametri: meta.params.lang = "ko"
  3. Convalida: il CMS verifica che "ko" esista nello schema language
  4. Valutazione CEL: script come documents.get("greeting", meta.params.lang) restituiscono il contenuto in coreano
  5. Risposta: i blocchi localizzati vengono restituiti al client

Appendice B: riferimento tecnico

Interfaccia CelMeta (TypeScript)

interface CelMeta {
  /** Codice locale corrente (ad esempio 'en-US') */
  locale: string;
  /** Parametri di route estratti dall'URL */
  params: Record<string, string>;
  /** Segmenti del percorso URL */
  segments: string[];
  /** ID del documento corrente (se si modifica un documento esistente) */

Algoritmo di estrazione dei parametri

La funzione extractParams elabora i percorsi URL:

Pattern: /{country}/{lang}/products
Path:    /us/en/products

Algorithm:
1. Normalizza entrambi (rimuovi gli slash finali)
2. Suddividi in segmenti: ["us", "en", "products"] e ["{country}", "{lang}"

Formati di associazione dei parametri supportati

// Associazione semplice (usa il campo "code" per la ricerca)
{ "lang": "language" }

// Associazione dettagliata (campo slug personalizzato)
{
  "lang": {
    "schemaName": "language",
    "slugField": "code"
  },
  "slug": {
    "schemaName": "article",
    "slugField": "slug"

Priorità di ricerca dei documenti

Quando si recupera tramite documents.get(schema, identifier):

  1. Corrispondenza UUID: se l'identificatore è un UUID valido, recupera tramite id
  2. Campo code: controlla il campo content.code
  3. Campo slug: controlla il campo content.slug
  4. Corrispondenza del titolo: controlla il campo title

Questo consente riferimenti flessibili ai documenti tramite qualsiasi identificatore univoco.

]
] }
"headline"
:
"ようこそ"
,
"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('/');
// Ottieni la route e i parametri risolti
const { route, resolvedParams } = await client.routes.getRouteByPath.query({
websiteId: process.env.CMS_WEBSITE_ID!,
path,
});
// Recupera i blocchi per la route
const blocks = await client.blocks.getBlocks.query({
websiteId: process.env.CMS_WEBSITE_ID!,
blockIds: route.block_ids,
// Passa i parametri risolti per il contesto 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: {},
},
});
// Renderizza i blocchi
return (
<main>
{blocks.map((block) => (
<BlockRenderer
key={block.id}
block={block}
routeParams={resolvedParams}
language={resolvedParams?.lang?.value}
/>
))}
</main>
);
}
docId: string | null;
/** Titolo del documento corrente */
title: string;
}
,
"products"
]
3. Confronta il numero di segmenti (devono essere uguali)
4. Per ogni coppia di segmenti:
- Se il pattern inizia con : oppure {}, estrai come parametro
- Altrimenti, deve corrispondere esattamente
5. Restituisci: { country: "us", lang: "en" }
}
}