Tell AI tools what each collection is FOR with `kind` and `description` fields.
Why this matters
When you write a cms.config.ts, the schema fields tell the CMS what a collection holds — but they don't tell anything what it's for. Is a team collection a page (with its own URL)? A data source for another page's template? Or a form for visitor submissions?
The inline chat, MCP tools, and any AI agent building or editing your site need this context to make good decisions. Without it, they guess — and often guess wrong.
Example of what goes wrong without metadata:
- Chat generates SEO metadata for a
teamcollection that has no URL (wasted tokens, never used) - Chat adds a "View" button that leads to 404 because there's no rendered page
- Chat remaps
body→biofor a team member, corrupting the field - Chat triggers a full site build after updating a single
globalsrecord (unnecessary)
Two new fields
As of F127, CollectionConfig accepts two optional fields:
defineCollection({
name: 'team',
label: 'Team Members',
kind: 'data', // ← NEW
description: 'Team members rendered on /about.', // ← NEW
fields: [/* ... */],
});Both are optional and backwards compatible. Collections without kind default to page behavior — exactly what the chat does today. But you should populate them on every new collection.
The five kinds
page (default)
The collection produces indexable pages with URLs. Each document is a standalone page that appears in the sitemap, needs SEO metadata, and has a preview URL.
Use for: blog posts, landing pages, documentation articles, marketing pages.
Chat behavior: Full treatment — SEO generation, View pill, body/content remapping, site rebuild after changes.
defineCollection({
name: 'posts',
label: 'Blog Posts',
urlPrefix: '/blog',
kind: 'page',
description: 'Long-form blog articles. Each post has its own URL and appears in the RSS feed.',
fields: [/* ... */],
});snippet
Reusable text fragments that get embedded in other content via the <!-- snippet not found: slug --> token (see Snippet Embeds). They have no standalone URL.
Use for: CTAs, disclaimers, author bios, boilerplate text reused across posts.
Chat behavior: No SEO, no View pill, still builds (host pages need to re-render with the updated snippet).
defineCollection({
name: 'snippets',
label: 'Snippets',
kind: 'snippet',
description: 'Reusable text fragments embedded in posts via `{{snippet:slug}}`. Used for disclaimers, CTAs, and author bios.',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'content', type: 'richtext', required: true },
],
});data
Records that are rendered on OTHER pages via loops. They're data sources, not pages themselves.
Use for: team members, testimonials, FAQ items, products, portfolio projects, job openings.
Chat behavior: No SEO, no View pill, no body/content remapping (uses exact field names from your schema), still builds.
defineCollection({
name: 'team',
label: 'Team Members',
kind: 'data',
description: 'Team members. Rendered in a grid on /about and as bylines on blog posts.',
fields: [
{ name: 'name', type: 'text', required: true },
{ name: 'role', type: 'text' },
{ name: 'bio', type: 'textarea' },
{ name: 'photo', type: 'image' },
],
});form
Form submissions — contact forms, lead capture, applications. These are created by end users via frontend forms, never by AI or editors.
Use for: contact-submissions, lead-forms, applications, newsletter-signups.
Chat behavior: READ-ONLY — AI cannot create, update, or delete documents in form collections. Listing and searching is allowed.
defineCollection({
name: 'contact-submissions',
label: 'Contact Form',
kind: 'form',
description: 'Submissions from the /contact form. Created by visitors via frontend form. Reviewed by sales team.',
fields: [
{ name: 'name', type: 'text', required: true },
{ name: 'email', type: 'text', required: true },
{ name: 'message', type: 'textarea', required: true },
{ name: 'submittedAt', type: 'date' },
],
});global
Site-wide configuration stored as a single record. No URL, no indexing, just settings.
Use for: footer content, social links, analytics IDs, site-wide announcements.
Chat behavior: Treated as settings. No SEO, no View pill, single-record mode.
defineCollection({
name: 'globals',
label: 'Site Settings',
kind: 'global',
description: 'Site-wide configuration: footer text, social links, analytics IDs, cookie banner copy. Single record only.',
fields: [/* ... */],
});Note: The name
globalsis a CMS convention. Never usesettings,config,admin,media, orinteractivesas collection names — they conflict with built-in admin UI panels.
Writing good descriptions
A good description answers three questions:
- What is this? ("Team members.", "Customer testimonials.")
- Where does it appear? ("Rendered on /about.", "Looped on the homepage hero.")
- What references it? ("Referenced by posts.author field.")
Good:
"Team members. Referenced by posts.author field. Rendered on /about and as bylines on posts."
Bad:
"Team stuff" — too vague, tells AI nothing
"A collection of team members" — restates the name, no new information
Backwards compatibility
Both fields are optional. Sites written before F127 continue working exactly as before — undefined kind defaults to page behavior. But there's no reason not to populate them on every collection you add going forward.
The Site Config Validator will show a soft warning when a collection is missing description. It's advisory, not blocking.
Related
- Collections — managing documents
- Collection Naming — reserved names to avoid
- Snippet Embeds — the
snippetkind in action - AI Builder Guide — scaffolding sites with AI