Praktyczny przewodnik po pisaniu wyrażeń CEL w CMS.
Praktyczny przewodnik po pisaniu wyrażeń CEL w CMS.
CEL (Common Expression Language) to lekki język skryptowy wbudowany w nasz CMS. Umożliwia pisanie dynamicznych wyrażeń, które mogą pobierać dane z dokumentów, odczytywać parametry URL i obliczać wartości w locie.
Tak wygląda działanie skryptu CEL w czasie wykonywania:
Twój skrypt Mechanizm Wynik
| | |
v v v
documents.get("article", "intro") --> Pobiera z bazy danych --> { headline: "Welcome", body: "..." }
.headline --> Wyodrębnia pole --> "Welcome"
Traktuj CEL jak język zapytań tylko do odczytu. Nie może zmieniać niczego w bazie danych — jedynie odczytuje dane i zwraca wynik obliczeń. Dzięki temu można go bezpiecznie używać w dowolnym miejscu CMS.
Każde wyrażenie CEL ma dostęp do trzech elementów:
| Obiekt | Co to jest | Przykład |
|---|---|---|
documents | Pobierz dowolny dokument z CMS | documents.get("country", "us") |
meta | Informacje o bieżącym żądaniu (lokalizacja, parametry URL) | meta.locale, meta.params.slug |
schema | Definicje pól bieżącego dokumentu | schema.fields |
Najpotężniejszą funkcją CEL jest pobieranie dokumentów z dowolnego miejsca w CMS.
Składnia: documents.get(schemaName, identifier)
Załóżmy, że masz dokument article zapisany z identyfikatorem "welcome-post":
// Zapisany w CMS jako: article / welcome-post
{
"headline": "Welcome to Our Platform",
"author": "Sarah Chen",
"body": "We're excited to announce...",
"tags": ["announcement", "news"]
}
Aby pobrać cały dokument:
documents.get("article", "welcome-post")
Zwraca:
{
"headline": "Welcome to Our Platform",
"author": "Sarah Chen",
"body": "We're excited to announce...",
"tags": ["announcement", "news"]
}
Aby pobrać tylko nagłówek:
documents.get("article", "welcome-post").headline
Zwraca: "Welcome to Our Platform"
Aby pobrać autora:
documents.get("article", "welcome-post").author
Zwraca: "Sarah Chen"
Gdy Twoja strona ma dynamiczne trasy (na przykład /articles/[slug]), możesz użyć meta.params, aby pobrać parametr z adresu URL i załadować właściwy dokument.
Jeśli ktoś odwiedzi /articles/welcome-post:
documents.get("article", meta.params.slug).headline
Zwraca: "Welcome to Our Platform"
Tak budujesz dynamiczne strony — ten sam skrypt CEL działa dla dowolnego artykułu, wykorzystując slug z adresu URL.
Składnia: documents.find(schemaName) lub documents.find(schemaName, filter)
// Pobierz wszystkie kraje
documents.find("country")
Zwraca:
[
{ "code": "us", "name": "United States", "flag": "US" },
{ "code": "sa", "name": "Saudi Arabia", "flag": "SA" },
{ "code": "gb", "name": "United Kingdom", "flag": "GB" }
// Pobierz kraje z filtrem
documents.find("country", { "where": { "code": "us" } })
Zwraca:
[
{ "code": "us", "name": "United States", "flag": "US" }
]
Masz hero-block, który powinien wyświetlać nagłówek pobrany z dokumentu article.
Twój dokument article (identyfikator: "homepage-hero"):
{
"headline": "Build Faster, Ship Smarter",
"subheadline": "The modern CMS for developers"
}
Skrypt CEL w polu tytułu bloku hero:
documents.get("article", "homepage-hero").headline
Wynik: Hero wyświetla "Build Faster, Ship Smarter"
Budujesz stronę pod /countries/[code] i chcesz wyświetlić pełną nazwę kraju.
Twoje dokumenty country:
// country / us
{ "code": "us", "name": "United States", "flag": "US", "languages": ["en", "es"] }
// country / sa
{ "code": "sa", "name": "Saudi Arabia", "flag": "SA", "languages": ["ar", "en"
Skrypt CEL:
documents.get("country", meta.params.code).name
Gdy ktoś odwiedzi /countries/us:
meta.params.code = "us""United States"Gdy ktoś odwiedzi /countries/sa:
meta.params.code = "sa""Saudi Arabia"Wyświetlaj różne nagłówki w zależności od ustawień regionalnych użytkownika.
meta.locale == "ar-SA" ? "مرحبا بكم" : "Welcome"
Jeśli ustawienie regionalne to "ar-SA": Zwraca "مرحبا بكم"
Jeśli ustawienie regionalne jest inne: Zwraca "Welcome"
Twój article ma pole countryCode, a Ty chcesz pobrać pełną nazwę kraju.
Dokument article:
{ "headline": "News from the US", "countryCode": "us" }
Skrypt CEL:
documents.get("country", documents.get("article", "us-news").countryCode).name
Co się dzieje:
documents.get("article", "us-news") zwraca { "headline": "News from the US", "countryCode": "us" }.countryCode wyodrębnia "us"documents.get("country", "us") zwraca { "code": "us", "name": "United States", ... }.name wyodrębnia "United States"Wynik: "United States"
Jeśli dokument może nie istnieć, możesz podać wartość zapasową:
documents.get("article", meta.params.slug) != null
? documents.get("article", meta.params.slug).headline
: "Article Not Found"
Albo sprawdź, czy konkretne pole istnieje:
documents.get("article", "intro").author != null
? documents.get("article", "intro").author
: "Unknown Author"
Twój artykuł ma tagi i chcesz sprawdzić, czy istnieje konkretny tag:
"featured" in documents.get("article", "welcome-post").tags
Zwraca: true, jeśli artykuł ma tag "featured"
Pobierz pierwszy tag:
documents.get("article", "welcome-post").tags[0]
Zwraca: "announcement" (pierwszy tag)
Policz tagi:
size(documents.get("article", "welcome-post").tags)
Zwraca: 2 (liczba tagów)
Trasy parametryczne są kluczem do budowania dynamicznych, zlokalizowanych stron. Gdy zdefiniujesz wzorzec trasy, taki jak /{lang}/landingPage, CMS wyodrębnia parametry z adresu URL i udostępnia je przez meta.params.
Definicja wzorca trasy:
Trasy używają składni :paramName lub {paramName} do definiowania dynamicznych segmentów:
| Wzorzec | Przykładowy URL | Wyodrębnione parametry |
|---|---|---|
/:lang/landingPage | /ko/landingPage | { lang: "ko" } |
/{country}/{lang}/products | /us/en/products | { country: "us", lang: "en" } |
/articles/:slug | /articles/welcome-post | { slug: "welcome-post" } |
Powiązania parametrów: Każdy parametr trasy można powiązać ze schematem dokumentu w celu walidacji:
{
"pattern": "/{lang}/landingPage",
"param_bindings": {
"lang": "language"
}
}
To powiązanie informuje CMS, że:
lang z adresu URLlanguage (szuka dokumentu, w którym content.code się zgadza)Konfiguracja trasy:
/{lang}/landingPage/{lang}/landingPage{ "lang": "language" }Twoje dokumenty greeting:
// greeting / ko
{ "code": "ko", "headline": "환영합니다", "subheadline": "우리 플랫폼에 오신 것을 환영합니다" }
// greeting / en
{ "code": "en", "headline": "Welcome", "subheadline": "Welcome to our platform" }
// greeting / ja
{ "code": "ja", "headline"
Skrypt CEL do pobrania zlokalizowanej treści:
documents.get("greeting", meta.params.lang).headline
Jak to się rozwiązuje:
| URL | meta.params.lang | Wynik |
|---|---|---|
/ko/landingPage | "ko" | "환영합니다" |
/en/landingPage | "en" | "Welcome" |
/ja/landingPage | "ja" | "ようこそ" |
Dla tras takich jak /{country}/{lang}/products:
Konfiguracja trasy:
{
"pattern": "/{country}/{lang}/products",
"param_bindings": {
"country": "country",
"lang": "language"
}
}
Skrypty CEL:
// Pobierz nazwę kraju
documents.get("country", meta.params.country).name
// Pobierz zlokalizowaną listę produktów na podstawie kraju
documents.find("product", { "where": { "country": meta.params.country } })
// Połączone: pokaż powitanie specyficzne dla kraju w języku użytkownika
documents.get("greeting", meta.params.lang).headline + " from " + documents.get("country", meta.params.country).name
Kaskada walidacji:
CMS weryfikuje parametry hierarchicznie. Dla tras /{country}/{lang}:
country względem schematu countrylang względem schematu languagelang znajduje się w tablicy country.languages[] (walidacja hierarchiczna)meta.segments udostępnia surową ścieżkę URL jako tablicę, co jest przydatne, gdy potrzebujesz dostępu do segmentów po indeksie bez nazwanych parametrów.
Jak to działa:
| Ścieżka URL | meta.segments |
|---|---|
/articles/tech/ai-news | ["articles", "tech", "ai-news"] |
/ko/landingPage | ["ko", "landingPage"] |
/us/en/products/featured | ["us", "en", "products", "featured"] |
/ | [] |
| Zastosowanie | Najlepsze podejście |
|---|---|
| Nazwane parametry z wzorca trasy | meta.params.lang |
| Dostęp oparty na pozycji | meta.segments[0] |
| Sprawdzenie głębokości ścieżki | size(meta.segments) |
| Sprawdzenie, czy ścieżka zawiera segment | "admin" in meta.segments |
// Pobierz pierwszy segment (często kod języka)
meta.segments[0]
// Sprawdź głębokość ścieżki
size(meta.segments) > 2 ? "deep" : "shallow"
// Sprawdź, czy jesteśmy w sekcji administracyjnej
"admin" in meta.segments ? "admin mode" : "public mode"
// Fallback: użyj segmentu, jeśli parametr nie jest powiązany
has(meta.params.lang) ? meta.params.lang : meta.segments[0]
Obiekt meta zawiera cały kontekst dotyczący bieżącego żądania:
| Właściwość | Typ | Opis | |
|---|---|---|---|
meta.locale | string | Aktualny kod ustawień regionalnych (np. "en-US", "ko-KR", "ar-SA") | |
meta.params | Record<string, string> | Parametry trasy wyodrębnione z wzorca URL | |
meta.segments | string[] | Ścieżka URL podzielona na segmenty | |
meta.docId | string \\ | null | Bieżący UUID dokumentu (null dla nowych dokumentów) | |
meta.title | string | Aktualny tytuł dokumentu |
Kod ustawień regionalnych stosuje format BCP 47 (język-region):
// Sprawdź ustawienia regionalne dla języków RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"
// Pobierz tylko część językową
meta.locale.split("-")[0] // Nieobsługiwane – użyj meta.params.lang zamiast tego
Parametry tras są zawsze łańcuchami znaków. CMS weryfikuje je względem powiązanych schematów przed ewaluacją:
// Dostęp do nazwanego parametru
meta.params.lang // "ko"
meta.params.country // "us"
meta.params.slug // "welcome-post"
// Sprawdź, czy parametr istnieje
has(meta.params.category) // true/false
// Użyj przy pobieraniu dokumentu
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)
Surowe segmenty adresu URL jako tablica:
// Dostęp po indeksie (0-based)
meta.segments[0] // Pierwszy segment
meta.segments[1] // Drugi segment
// Sprawdź długość
size(meta.segments) // Liczba segmentów
// Sprawdź, czy segment istnieje
"products" in meta.segments // Czy ścieżka zawiera "products"?
Bieżący UUID dokumentu, przydatny dla skryptów odnoszących się do siebie samych:
// Dostępny tylko podczas edycji istniejących dokumentów
meta.docId != null ? "editing" : "creating new"
// Użyj w logice warunkowej
meta.docId != null ? documents.get("article", meta.docId).status : "draft"
Bieżący tytuł dokumentu:
// Użyj do wyświetlania
"Editing: " + meta.title
// Warunek oparty na tytule
meta.title.contains("Draft") ? "work in progress" : "published"
Dla bardziej przejrzystej składni, gdy schemat jest znany, ale identyfikator jest dynamiczny:
// Tradycyjne podejście
documents.get("airports", meta.params.code).name
// Użycie ref() – schemat oddzielony od dynamicznego identyfikatora
documents.ref("airports").get(meta.params.code).name
Obie metody są równoważne, ale ref() sprawia, że część dynamiczna jest bardziej przejrzysta.
documents.get("schema", "identifier") // Pobierz jeden dokument
documents.get("schema", "id").fieldName // Pobierz konkretne pole
documents.find("schema") // Pobierz wszystkie dokumenty
documents.find("schema", { "where": {...}}) // Zapytanie z filtrem
documents.ref("schema").get(identifier) // Łańcuchowe wyszukiwanie
meta.locale // "en-US", "ar-SA" itp.
meta.params.xyz // Parametr URL o nazwie "xyz"
meta.segments // Ścieżka URL jako tablica: ["articles", "intro"]
meta.segments[0] // Pierwszy segment ścieżki
meta.docId // ID bieżącego dokumentu (lub null)
meta.title // Tytuł bieżącego dokumentu
// Porównania
== != < <= > >=
// Logika
&& || !
// Operator trójargumentowy (if-else)
condition ? valueIfTrue : valueIfFalse
// Przynależność
"value" in listOrMap
size(list) // Liczba elementów
size(string) // Długość łańcucha
"text".startsWith("te") // true
"text".endsWith("xt") // true
"text".contains("ex") // true
has(object.property) // Sprawdź, czy właściwość istnieje
Jeśli coś pójdzie nie tak, zobaczysz jeden z tych komunikatów:
| Błąd | Co oznacza |
|---|---|
SYNTAX_ERROR | Literówka w skrypcie (brakujący cudzysłów, zły operator) |
TYPE_ERROR | Mieszasz typy, które do siebie nie pasują |
RUNTIME_ERROR | Skrypt się uruchomił, ale napotkał problem (niezdefiniowana zmienna) |
FETCH_LIMIT_EXCEEDED | Pobierasz zbyt wiele dokumentów (maksymalnie 50) |
TIMEOUT | Skrypt trwał zbyt długo (maksymalnie 5 sekund) |
AST_DEPTH_EXCEEDED | Wyrażenie jest zbyt zagnieżdżone (maksymalna głębokość: 50) |
SCRIPT_TOO_LONG | Skrypt przekracza limit 5000 znaków |
Silnik CEL został zaprojektowany z myślą o rozszerzalności. Przyszłe możliwości obejmują:
// Przyszłość: wywoływanie usług zewnętrznych przez MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)
// Przyszłość: generowanie treści wspierane przez 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"])
Te możliwości zostaną dodane poprzez system zarejestrowanych funkcji, zachowując kompatybilność wsteczną z istniejącymi skryptami.
documents. lub meta. i edytor pokaże dostępne opcjedocuments.get("schema", "id"), a potem dodaj .fieldName!= null ? ... : ...documents.get() lub documents.find() liczy się do limitu 50 pobrańhas(meta.params.category) przed użyciemTen przewodnik krok po kroku tworzy wielojęzyczną stronę docelową dostępną pod /{lang}/landingPage.
W panelu administracyjnym CMS utwórz niestandardowy schemat o nazwie greeting:
{
"name": "greeting",
"fields": [
{ "name": "code", "type": "string", "required": true },
{ "name": "headline", "type": "string", "required": true },
{ "name": "subheadline"
Utwórz dokumenty dla każdego języka:
Dokument: greeting / ko
{
"code": "ko",
"headline": "환영합니다",
"subheadline": "우리 플랫폼에 오신 것을 환영합니다",
"ctaText": "시작하기",
"ctaUrl": "/ko/get-started"
}
Dokument: greeting / en
{
"code": "en",
"headline": "Welcome",
"subheadline": "Welcome to our platform",
"ctaText": "Get Started",
"ctaUrl": "/en/get-started"
}
Dokument: greeting / ja
{
"code": "ja",
"headline": "ようこそ",
"subheadline": "私たちのプラットフォームへようこそ",
"ctaText": "始める",
"ctaUrl": "/ja/get-started"
}
Utwórz trasę z następującą konfiguracją:
/{lang}/landingPage/{lang}/landingPage {
"lang": "language"
}
Dodaj blok hero do trasy z następującymi skryptami CEL dla każdego pola:
Pole nagłówka:
documents.get("greeting", meta.params.lang).headline
Pole podtytułu:
documents.get("greeting", meta.params.lang).subheadline
Pole tekstu CTA:
documents.get("greeting", meta.params.lang).ctaText
Pole adresu URL CTA:
documents.get("greeting", meta.params.lang).ctaUrl
Utwórz trasę catch-all w aplikacji Next.js:
// app/[...slug]/page.tsx
import { getCmsClient } from '@repo/renderer';
interface PageProps {
params: { slug: string[] };
}
export default async function Page({ params }: PageProps) {
Odwiedź te adresy URL, aby zobaczyć zlokalizowaną treść:
| URL | Oczekiwany nagłówek |
|---|---|
/ko/landingPage | 환영합니다 |
/en/landingPage | Welcome |
/ja/landingPage | ようこそ |
Gdy użytkownik odwiedza /ko/landingPage:
/{lang}/landingPagemeta.params.lang = "ko"languagedocuments.get("greeting", meta.params.lang) zwracają koreańską treśćinterface CelMeta {
/** Aktualny kod ustawień regionalnych (np. 'en-US') */
locale: string;
/** Parametry trasy wyodrębnione z adresu URL */
params: Record<string, string>;
/** Segmenty ścieżki URL */
segments: string[];
/** ID bieżącego dokumentu (jeśli edytujesz istniejący dokument) */
Funkcja extractParams przetwarza ścieżki URL:
Pattern: /{country}/{lang}/products
Path: /us/en/products
Algorithm:
1. Ujednolić oba (usunąć końcowe ukośniki)
2. Podzielić na segmenty: ["us", "en", "products"] i ["{country}", "{lang}", "products"]
// Proste powiązanie (używa pola "code" do wyszukiwania)
{ "lang": "language" }
// Szczegółowe powiązanie (niestandardowe pole slug)
{
"lang": {
"schemaName": "language",
"slugField": "code"
},
"slug": {
"schemaName": "article",
"slugField": "slug"
}
Podczas pobierania przez documents.get(schema, identifier):
idcontent.codecontent.slugtitleDzięki temu możesz odwoływać się do dokumentów za pomocą dowolnego unikalnego identyfikatora.