Entity Content Structure
The structure every content record — a blog post, a document, any content-as-data artifact — follows: how you write one in a file (YAML, JSON, Markdown frontmatter, BibTeX), and the shape your component receives it in.
A data-schema (see Data Schemas) is the typed skeleton of this structure: the schema describes what a record of that type looks like; this page describes how an actual record is written out, and read.
At a glance
A record is a single object. Its keys come from the sections declared in the record's schema, and each section's value follows the section's kind: a single section is an object, a multi section is an array of records.
A schema with one section — the fields: form, or sections: with a single one — has a flat record, fields at the top level:
title: "Rust 101"
summary: "Learn Rust from scratch"
published: 2026-05-01A schema with more than one section is written by section, each section under its own name:
identity: # a `single` section → object
title: "Rust 101"
published: 2026-05-01
modules: # a `multi` section → array (index = order)
- title: "Getting Started"
lessons: # a nested section → an inline field of child records
- { title: "Install" }
- { title: "Hello World" }
- title: "Going Deeper"
lessons:
- { title: "Macros" }Sections become object keys; their kind decides whether the value is an object or an array; a nested section (a child section declared under a parent) becomes an inline field on the parent's records.
Only a schema with one section is written flat. In any other, every field goes under the name of its section — a field written at the top of such a record stops the build and a push, with a message that names the section it belongs in. A section is a namespace: two sections may declare fields of the same name.
What a component receives
A record reaches a component in one of two shapes — the same on a static site and on one whose records come from a backend. The component's section type says which it expects, per content.data key, in its meta.js (Component Metadata → Briefs or whole records):
- A brief, the default —
data: { articles: '@std/article' }: the fields of the schema's brief — the section markedbrief: true, else the firstsinglesection — at the top of the record. The brief section is not named. - The record whole —
data: { articles: { schema: '@std/article', whole: true } }: the record as it is stored, each section under its own name, the brief's included.
Both carry $name, the record's name (below); $tags and $label when its entry in records/folder.yml gives them (Folders); and a reference as the record it points at, reduced to its brief: { entity, brief }, entity being that record's id where it has one.
// records/std/article/hello.md as a brief — '@std/article'
{ $name: 'hello', title: 'Hello', date: '2026-05-01' }
// the same record whole — { schema: '@std/article', whole: true }
{
$name: 'hello',
brief: { title: 'Hello', date: '2026-05-01' }, // the card, under its section's name
body: { content: { type: 'doc', … } }, // the markdown body
}Neither shape mixes the two: a brief holds one section's fields, a whole record holds sections. That is what lets a brief field share its name with a section — details the field, details.pages the section.
A schema of one section — the fields: form — has a brief that is the whole record: its fields at the top as a brief, and under brief whole, so a component reading such records has no need of whole: true. A schema with no brief (only multi sections) is answered whole, even as a brief.
What each key gets. A list carries briefs, or each record whole for a key declared whole: true. A parametric page receives its record as its component declares: the brief its query's list holds, or the record whole from its own source — one record, for a key declared single: true. A key declared whole: true whose source cannot answer one record whole is null. Records of an external query have no schema, and so no brief: they arrive as their source answers them.
Sections and nesting
A schema's sections form a tree. Each section's kind determines its shape in a record:
| Kind | Shape in the record | Example |
|---|---|---|
single | object | identity: { title: "…" } |
multi | array of records; index = order | modules: [ { … }, … ] |
binder | object whose keys are its child sections (organizational; no fields of its own) | contributions: { publications: [ … ] } |
A nested section is an inline field. When a section declares child sections, each child appears as an inline field on the parent's record(s), keyed by the child section's name — so a single parent declaring a multi child looks like parent: { childMulti: [ …records… ], …other parent fields… }. Cross-section parent/child relationships are pure structure — no back-references, no path bookkeeping. The data tree mirrors the schema tree.
Field values
Field values are the data you actually write. They follow the schema's field types (see Data Schemas for the full type catalogue) — no wrappers, no encoding:
| Field type | Value shape |
|---|---|
string, text, int, decimal, bool | The raw value |
text with format: markdown | The raw source string (a rich-content body) |
date, datetime | ISO-8601 string (e.g. 2026-05-01, 2026-05-01T12:00:00Z) |
file | A path or URL to the file |
array (of scalars) | The native array ([a, b, c]) |
ref | The name of the record it points at (Names); a list of names for a many reference |
A localized field of any text kind | { <locale>: value } — e.g. title: { en: "Hello", fr: "Bonjour" } |
For a localized field you can write the value as a bare string in your source file (in the site's source locale); translations live in the locales/ folder (see Internationalization).
Names
Every record has a name, which a component reads as $name: the file's name without its extension — or, for a BibTeX entry, its cite key. Names may repeat; what identifies a record is its $uuid. By default a [slug] parametric page places its URL's :slug in its query as a condition on $name, and when several records match, it takes one of them. A ref field can name a record by it. A file holds one record, and nothing inside it renames the record: a slug: key at its top, or a list of records, stops the build — to rename a record, rename its file. The record a component receives carries its name as $name only; a slug inside a section is one of that section's fields, the author's data like any other.
Per-format authoring
The same structure, four authoring formats.
YAML
# records/product/widget-x.yml → slug "widget-x"
title: "Widget X"
price: 9.99
published: 2026-04-12JSON
{
"title": "Widget X",
"price": 9.99,
"published": "2026-04-12"
}Markdown (frontmatter + body)
---
title: "Hello, World"
published: 2026-04-12
---
# Welcome
The body becomes the value of the schema's content field.The frontmatter is the record's data, written flat or by section like any other record; the body is the value of the schema's content field — a markdown field, which holds the markdown source, or a richtext one, which holds it as a rich document — in whichever section declares it. @std/article declares content in its body section:
---
brief:
title: "Hello, World"
date: 2026-04-12
---
# Welcome
The body is `body.content`.BibTeX
The entry's cite key is its slug:
@article{smith2026,
title = {On Rust traits},
author = {Smith, A.},
year = {2026},
}Relationship to data-schemas
A data-schema and a record of that type share their shape. The schema is the typed skeleton; the record fills it in with values. Reading a schema, you can predict what a record looks like; reading a record, you can read the schema's tree out of it. When you change a schema, records evolve along the same tree — that's the value of the mirroring.
# schema — foundation/schemas/course.yml
name: course
sections:
identity:
kind: single
brief: true
fields:
title: { type: string }
modules:
kind: multi
fields:
title: { type: string }
sections:
lessons:
kind: multi
fields:
title: { type: string }# a record of that schema
identity: { title: "Rust 101" }
modules:
- title: "Getting Started"
lessons:
- { title: "Install" }Restrictions
- Within a section, no field key may equal one of the section's child-section names — a name refers to either a field or a child section, never both.
- A slug is unique within its schema.