Skip to content

Collections

Collections are defined in src/cms/collections/ and registered in src/cms/cms.config.ts. Each collection becomes a database table, a set of TypeScript types, Zod validators, and an admin UI.

src/cms/collections/posts.ts
import { defineCollection, fields } from "../core/define";
export default defineCollection({
slug: "posts",
labels: { singular: "Post", plural: "Posts" },
timestamps: true,
drafts: true,
versions: { max: 20 },
fields: {
title: fields.text({ required: true }),
slug: fields.slug({ from: "title", unique: true, admin: { position: "sidebar" } }),
body: fields.richText(),
author: fields.relation({ collection: "authors", admin: { position: "sidebar" } }),
},
});

Register it in src/cms/cms.config.ts:

import { defineConfig } from "./core/define";
import posts from "./collections/posts";
export default defineConfig({
database: { dialect: "sqlite" },
collections: [posts],
});
OptionTypeDescription
slugstringURL-safe identifier, used as table name prefix
labels{ singular, plural }Display names in admin
singletonbooleanSingle document (e.g., front page)
timestampsbooleanAuto _createdAt / _updatedAt
draftsbooleanEnable draft/published status
versions{ max: number }Keep version snapshots
pathPrefixstringURL prefix for public pages (e.g., "blog")
previewboolean | stringEnable preview link (string for static URL)
labelFieldstringField used as document display name
viewsobjectList column config (see Admin UI)
authbooleanEnable login/session handling

By default, the admin uses the title field as the document display name (in relation selects, DataTable, breadcrumbs). If your collection doesn’t have a title field, or you want a different field, set labelField:

defineCollection({
slug: "authors",
labels: { singular: "Author", plural: "Authors" },
labelField: "name",
fields: {
name: fields.text({ required: true }),
title: fields.text(), // work title, not the display name
},
});

Fallback chain: labelField → field named title → field named name → first text field.

defineCollection({
slug: "front-page",
labels: { singular: "Front Page", plural: "Front Page" },
singleton: true,
fields: { ... },
})

Singletons show under “Singles” in the sidebar. One document per collection.

Enable locales in cms.config.ts and mark fields as translatable:

src/cms/cms.config.ts
export default defineConfig({
locales: {
default: "en",
supported: ["en", "fi"],
},
collections: [posts],
});
src/cms/collections/posts.ts
fields: {
title: fields.text({ translatable: true }),
body: fields.richText({ translatable: true }),
category: fields.select({ options: ["tech", "design"] }), // not translated
}

Translatable fields get a separate _translations table. Non-translatable fields stay on the main table. The admin shows a language switcher on edit pages.