BuildBot

Structured Content

Content modeling

Lesson 1 of 5

What you'll learn

  • Define schema types in code with defineType and defineField
  • Choose between documents (standalone, referenceable) and objects (inline shapes)
  • Add references, arrays, and validation rules to a content model

Sanity stores your content as plain JSON documents in a hosted Content Lake. There's no database schema on the server — the shape of your content is defined in code, in your Studio project, using defineType and defineField. That means your content model is versioned, reviewable, and refactorable like any other source file.

import {defineType, defineField} from 'sanity'

export const post = defineType({
  name: 'post',
  title: 'Post',
  type: 'document',
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      validation: (rule) => rule.required().max(120),
    }),
    defineField({
      name: 'slug',
      type: 'slug',
      options: {source: 'title'},
      validation: (rule) => rule.required(),
    }),
    defineField({
      name: 'author',
      type: 'reference',
      to: [{type: 'author'}],
    }),
    defineField({
      name: 'tags',
      type: 'array',
      of: [{type: 'string'}],
    }),
  ],
})

Document vs object

A document type is a standalone record: it gets its own _id, shows up in the Studio's content lists, and can be referenced from other documents. An object type is an inline shape — it has no _id, and it lives and dies embedded inside whatever document holds it:

export const seo = defineType({
  name: 'seo',
  title: 'SEO',
  type: 'object',
  fields: [
    defineField({name: 'metaTitle', type: 'string'}),
    defineField({name: 'metaDescription', type: 'text'}),
  ],
})

The rule of thumb: if two documents need to share a piece of content, make it a document and point at it with a reference field. A reference stores {_ref: '<id>'} — a pointer, not a copy — so renaming an author updates every post at once. If the content only ever belongs to one parent (like a post's SEO settings), make it an object.

Validation rules run in the Studio as editors type, and block publishing when they fail. They're functions of a rule builder — required(), max(), min(), custom checks — attached per field.

Model for meaning, not layout

Name types after what content is (author, testimonial), never where it renders (leftColumn, heroBox). Layouts change every redesign; a meaning-based model survives all of them.

The challenge is a JS model of schema validation: field rules as plain functions, run over an inline array of documents, exactly the way the Studio gates publishing.

Schema validation (JS model)

Run it. Each rule is a function from a field value to an error (or null). Documents that produce any error would be blocked from publishing.

Loading editor…
Knowledge check

When should a piece of content be its own document type rather than an object?

Next: querying the Content Lake with GROQ — filters, projections, and joins by reference.

Saved on this device. Sign in to sync your progress everywhere.