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.
Basic collection
Section titled “Basic collection”import { defineCollection, fields, hasRole } from "@kidecms/core";
export default defineCollection({ slug: "posts", labels: { singular: "Post", plural: "Posts" }, timestamps: true, drafts: true, versions: { max: 20 }, access: { publish: hasRole("admin"), }, fields: { title: fields.text({ required: true }), slug: fields.slug({ from: "title", 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 "@kidecms/core";import posts from "./collections/posts";
export default defineConfig({ database: { dialect: "sqlite" }, collections: [posts],});Collection options
Section titled “Collection options”| Option | Type | Default | Description |
|---|---|---|---|
slug |
string |
— | URL-safe identifier, used as table name prefix |
labels |
{ singular, plural } |
— | Display names in admin |
singleton |
boolean |
false |
Single document (e.g., front page) |
timestamps |
boolean |
true |
Auto _createdAt / _updatedAt, added unless timestamps: false is set |
drafts |
boolean |
false |
Enable draft/published status |
versions |
{ max: number } |
— | Keep version snapshots |
pathPrefix |
string |
— | URL prefix for public pages (e.g., "blog") |
preview |
boolean | string |
false |
Enable preview link (string for static URL) |
labelField |
string |
title |
Field used as document display name |
access |
object |
— | Role-based access rules (see Access Control) |
hooks |
object |
— | Lifecycle hooks (see Hooks) |
views |
object |
— | List column config (see Admin UI) |
admin |
object |
— | Sidebar grouping, icon, order, and visibility |
auth |
boolean |
false |
Mark an authentication-sensitive collection |
searchable |
boolean | { fields: string[] } |
— | Controls full-text search inclusion |
Seed content is not a collection option. Define it in src/cms/seed.ts (keyed by collection slug) and load it with pnpm cms:seed.
The built-in login flow uses the users collection marked with auth: true. Auth collections receive stricter default access rules, automatic password hashing, and credential-field filtering. See Authentication before changing this boundary.
Admin sidebar
Section titled “Admin sidebar”Use collection-level admin options to keep large projects organized:
defineCollection({ slug: "case-studies", labels: { singular: "Case Study", plural: "Case Studies" }, admin: { group: "Marketing", icon: "Star", weight: 20, }, fields: { ... },});| Option | Type | Description |
|---|---|---|
group |
string |
Sidebar group label. Built-ins include Content, Library, Team |
icon |
string |
Lucide icon name used in the sidebar |
weight |
number |
Sort order inside the group. Lower appears first |
sidebar |
boolean |
Set false to hide from the sidebar |
Custom groups are collapsible and remember their open/closed state in the browser. Hidden collections still work in relations, search, and the local API.
Label field
Section titled “Label field”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 → first field of any type.
Singletons
Section titled “Singletons”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.
Internationalization
Section titled “Internationalization”Enable locales in cms.config.ts and mark fields as translatable:
export default defineConfig({ locales: { default: "en", supported: ["en", "fi"], }, collections: [posts],});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.