CMS Fields
A CMS collection's shape is defined by its fields. Fields live in the collection's template page, inside meta.cms.fields — a map of field name to a small definition object. Each definition has a type and a handful of optional keys like label, required, and default. The fields you declare here are exactly the fields you can edit on each item and render in your templates.
See CMS for how a collection's template page, schema, and content items fit together. This page is the reference for the field definitions themselves.
Where fields are declared
A collection is backed by a dynamic route at src/pages/<collection>/[slug].astro. Its meta.cms object carries the schema, and meta.cms.fields is the source of truth for the collection's fields. Here is the real fields block from this docs site's blog collection (src/pages/blog/[slug].astro):
const meta = {
title: "{{cms.title}}",
description: "{{cms.excerpt}}",
source: "cms",
cms: {
id: "posts",
name: "Blog Posts",
slugField: "slug",
urlPattern: "/blog/{{slug}}",
fields: {
title: { type: "string", label: "Title", required: true },
slug: { type: "string", label: "URL Slug", required: true },
excerpt: { type: "text", label: "Excerpt" },
content: { type: "rich-text", label: "Content" },
featuredImage: { type: "image", label: "Featured Image" },
publishedAt: { type: "date", label: "Publish Date" }
}
}
};Each entry is name: { type, label, required?, default?, … }. The key (title, excerpt, …) is the field name you reference everywhere else: in templates as cms.title, in collection lists as post.title, and in the editor's item form.
Field types
Every field needs a type. These are the available types:
Type | Description | Options |
|---|---|---|
| Single-line text (titles, names, short labels) | — |
| Multi-line plain text (excerpts, descriptions) | — |
| Formatted content, edited in a rich-text editor | — |
| A numeric value | — |
| A true/false toggle |
|
| An image file path | — |
| An ISO date / datetime string | — |
| A choice from a fixed list |
|
| A link to an item in another collection |
|
| A localized single-line string | — |
| A localized multi-line string | — |
A few notes that aren't obvious from the table:
string,text, andrich-textcover the three text shapes — one line, many lines, and formatted HTML. Userich-textwhenever the content needs headings, links, lists, or embedded components.selectrequires anoptionsarray. Withmultiple: trueit stores an array of values instead of a single value.referencerequires acollectionkey naming the target collection (itsmeta.cms.id). Withmultiple: trueit stores an array of references.i18nandi18n-textare the explicit localized counterparts ofstringandtext, storing a per-locale value. See Internationalization for how localized values are authored and resolved.
Field options
Beyond type, these keys configure a field:
label— the display name shown for the field in the editor (e.g."URL Slug"). Defaults to the field name.required—truemarks the field as mandatory when authoring an item.default— the value a new item starts with. Common onboolean(default: false) andnumber(default: 0).
select
A select field lists its allowed values in options. Add multiple: true to let an item hold several:
category: {
type: "select",
label: "Category",
options: ["tech", "design", "business"],
required: true
},
tags: {
type: "select",
label: "Tags",
options: ["featured", "new", "popular"],
multiple: true
}reference
A reference field links to an item in another collection, named by collection. This docs site's docs collection references a docs-categories collection for each article's category (from src/pages/docs/[slug].astro):
category: {
type: "reference",
label: "Category",
required: true,
collection: "docs-categories"
}The stored value is the referenced item's id. The referenced item is resolved at render time, so you reach its fields with dot notation — cms.category.name in a template, or post.author.name in a collection list:
<span>{i18n(cms.author.name)}</span>{postList.map((post, postIndex) => (
<span>{post.author.name}</span>
))}Add multiple: true for a reference that holds an array (for example, a relatedPosts field).
Schema keys that aren't fields
Two of the most-used collection settings live on meta.cms itself, not inside fields:
slugField— the name of the field whose value becomes each item's URL slug (usually"slug").urlPattern— the URL template for the collection, e.g."/blog/{{slug}}". The route directory follows it:/blog/{{slug}}is served fromsrc/pages/blog/[slug].astro.
So a field named slug is an ordinary string field; what makes it the slug is slugField: "slug" on the schema. See CMS for the full set of meta.cms keys.
Automatic fields
Every item gets system fields you never declare and shouldn't put in fields:
_id— a stable unique identifier (matches the file's name stem)._createdAt— an ISO timestamp set when the item is created._updatedAt— an ISO timestamp updated on each save.
Items are stored as JSON under src/content/<collection>/. An item file holds its declared fields plus these system fields. For example, an item in the posts collection looks like:
{
"_id": "introducing-meno",
"_createdAt": "2026-01-13T12:00:00.000Z",
"_updatedAt": "2026-01-15T11:04:12.178Z",
"title": "Introducing Meno",
"slug": "introducing-meno",
"excerpt": "Meet Meno: the AI-powered visual web builder…",
"content": { "type": "doc", "content": [] }
}An unpublished edit is saved as a sibling <name>.draft.json; the published file is the one without .draft. When both exist, the draft holds your in-progress changes until you publish.
Rendering fields in a template
Inside a collection's template page (or a collection list), you read fields off the current item — cms in a template page, your loop variable in a list. How you render depends on the field type:
Plain fields (
string,text,number,boolean,date,image, and theiri18nforms) render throughi18n(), which resolves the value for the current locale:
<Heading text={i18n(cms.title)} tag="1" size="1" cms={cms} />
<Text text={i18n(cms.publishedAt)} size="small" cms={cms} />Rich-text fields are structured objects, not strings, so they render through
richTextWithComponents()and<Fragment set:html={…}>— never a plain{i18n(cms.field)}, which would print[object Object]:
<Fragment set:html={richTextWithComponents(cms.content, cmsComponents)} />richTextWithComponents() also renders any components embedded in the rich text against the generated cmsComponents registry. Its import is derived boilerplate, like getStaticPaths — you don't hand-edit it. See CMS for the rendering pipeline in full.
To put fields to work end to end — defining a collection, authoring items, and rendering both a list and a detail page — follow Build a Blog.