Every piece of content in 0xCMS is a page, and a page's body is a lect — a single, schema-light JSON object that is universal, multi-language, and endlessly extensible. A blueprint describes its shape; the lect is the living document.
{
"_type": "mail_list",
"_pointers": {
"event": "21857528035279",
"edm": "21844625140640"
},
"allow_checkin": "yes",
"name": { "en": "01 FULL RUN" },
"_modifier": 1,
"_updated_at": "2026-06-24T15:54Z"
}
// 01 — the page
A page is the unit of content — an event, an article, a guest, an email template. It carries a little structured metadata in real columns, and all of its actual content in one lect column.
draft_pages rowdraft_pages — private, never public.page_versions — roll back to any point.page : lect :: record : document
// 02 — the lect
The lect is one JSON object with a handful of conventions. The same shape represents a page, a content block, a repeatable item, or a tag — so the editor, the renderer, and plugins all speak a single language.
Names the blueprint this lect follows — "event", "mail_list", a block type, etc.
Human text, stored as a language map: "name": { "en": "Gala", "zh-hant": "晚宴" }. Resolved per requested language.
Language-agnostic settings stored bare: "allow_checkin": "yes". Declared @field in the blueprint.
Links to other pages by id: "_pointers": { "event": "2185…" }. Soft relations — no rigid foreign keys.
Ordered nested lects — content blocks (_blocks) and repeatable groups ("session": [ … ]), each with its own _type, _weight, _name.
Metadata the CMS keeps alongside your content. Unknown keys are preserved untouched — the format is forward-compatible.
{
"_type": "event",
"start": "2026-06-25T19:00",
"_pointers": { "venue": "21857528035279" },
"name": { "mis": "Launch", "zh-hant": "發佈會" },
"body": { "en": "Join us at the edge…" },
"session": [
{
"_weight": 0,
"start": "19:00",
"name": { "en": "Keynote" }
}
],
"_blocks": [
{ "_type": "paragraph", "_weight": 0,
"body": { "en": "## Agenda…" } }
]
}
// 03 — multi-language
Text isn't a flat string — it's a map keyed by language. Adding a language is a config line, not a schema migration. A reader asks for a language; the resolver returns it, falling back to the neutral base.
// one field, every language
"name": {
"mis": "Launch", // base / neutral
"en": "Launch",
"zh-hant": "發佈會",
"zh-hans": "发布会"
}
mis base language0xCMS ships with languages: ['mis', 'en', 'zh-hant', 'zh-hans'] and a default of mis — the ISO 639 code for a language-neutral base. Resolution for a requested language returns that language's value, then falls back to mis, then to any value present, so partial translations never render blank.
// 04 — blueprint ⟷ lect
A blueprint is a tiny declarative array that describes a page type's fields. It is the schema; the lect is the document. The editor reads the blueprint to render the right widgets, and scaffolds an empty lect from it — but the lect itself stays free-form JSON.
blueprint.event = [
"@start:date/datetime",
"*venue",
"name:text/title",
"body:richtext/md",
{ session: [ "@start", "name" ] }
]
{
"_type": "event",
"start": "2026-06-25T19:00",
"_pointers": { "venue": "2185…" },
"name": { "mis": "Launch" },
"body": { "en": "## Hi" },
"session": [ { "start": "19:00", "name": {…} } ]
}
// blueprint tokens → what they become in the lect
One blueprint shapes many lects. The blueprint guides the editor and seeds defaults, but it never locks the document down: a lect can carry keys the blueprint never mentioned, so a plugin can stash its own data (a sent_edm log, an extra _pointers.event) and it round-trips untouched.
// 05 — extend
Because content lives in JSON behind a declarative blueprint, extending the model is additive — no ALTER TABLE, no downtime.
Append a token to the blueprint array. It shows up in the editor and in new lects instantly — existing lects simply don't have the key yet.
Add a code to languages. Every value field is already a map, so translators just fill a new key — no field is ever single-language.
A field:renderer resolves to a Liquid snippet at /snippets/pagefield/<renderer>. Drop in a template to ship a new field type — date pickers, maps, markdown.
Register a block type in blocks and list it in a blockList. Editors stack blocks into _blocks[] — each one a nested lect.
A *pointer links one page to another by id — group EDMs and guest lists under an event, reference a venue — all soft relations in _pointers.
A plugin Worker contributes whole page types, blocks and field snippets to the effective config over HTTPS — new content shapes with no CMS redeploy.
// see it in the editor
Open the live editor and watch a blueprint render into a multilingual lect.