webhouse.appwebhouse.appdocs

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 team collection 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 bodybio for a team member, corrupting the field
  • Chat triggers a full site build after updating a single globals record (unnecessary)

Two new fields

As of F127, CollectionConfig accepts two optional fields:

typescript
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.

typescript
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).

typescript
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.

typescript
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.

typescript
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.

typescript
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 globals is a CMS convention. Never use settings, config, admin, media, or interactives as collection names — they conflict with built-in admin UI panels.

Writing good descriptions

A good description answers three questions:

  1. What is this? ("Team members.", "Customer testimonials.")
  2. Where does it appear? ("Rendered on /about.", "Looped on the homepage hero.")
  3. 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.

Tags:SchemaAI Agents
Previous
Help panel, shortcuts & AI Chat tools
JSON API →Edit on GitHub →