Skip to main content
Schemas define the structure of your content in Sanity Studio. They are defined in JavaScript/TypeScript and compiled at runtime, providing a flexible and type-safe way to model your content.

Schema hierarchy

The schema system has a clear hierarchy:
Schema
└── Types
    ├── Document Types (top-level content)
    │   └── Fields
    │       ├── Primitive (string, number, boolean, etc.)
    │       ├── Complex (array, object, reference)
    │       └── Special (image, file, slug, etc.)
    └── Object Types (reusable field groups)

Document types

Document types are top-level content items that can be created, edited, and published:
import {defineType, defineField} from 'sanity'
import {UserIcon} from '@sanity/icons'

export default defineType({
  name: 'author',
  type: 'document',
  title: 'Author',
  icon: UserIcon,
  fields: [
    defineField({
      name: 'name',
      type: 'string',
      title: 'Name',
      validation: (Rule) => Rule.required(),
    }),
    defineField({
      name: 'bio',
      type: 'text',
      title: 'Biography',
    }),
    defineField({
      name: 'image',
      type: 'image',
      title: 'Profile Image',
      options: {
        hotspot: true,
      },
    }),
  ],
})

Field types

Primitive types

defineType({
  name: 'post',
  type: 'document',
  fields: [
    // String
    defineField({
      name: 'title',
      type: 'string',
      title: 'Title',
    }),
    
    // Text (multi-line string)
    defineField({
      name: 'excerpt',
      type: 'text',
      title: 'Excerpt',
      rows: 4,
    }),
    
    // Number
    defineField({
      name: 'views',
      type: 'number',
      title: 'View Count',
    }),
    
    // Boolean
    defineField({
      name: 'featured',
      type: 'boolean',
      title: 'Featured',
    }),
    
    // Date
    defineField({
      name: 'publishedAt',
      type: 'date',
      title: 'Published Date',
    }),
    
    // Datetime
    defineField({
      name: 'scheduledAt',
      type: 'datetime',
      title: 'Scheduled Time',
    }),
  ],
})

Complex types

defineType({
  name: 'post',
  type: 'document',
  fields: [
    // Array
    defineField({
      name: 'tags',
      type: 'array',
      title: 'Tags',
      of: [{type: 'string'}],
    }),
    
    // Object
    defineField({
      name: 'seo',
      type: 'object',
      title: 'SEO',
      fields: [
        defineField({
          name: 'metaTitle',
          type: 'string',
          title: 'Meta Title',
        }),
        defineField({
          name: 'metaDescription',
          type: 'text',
          title: 'Meta Description',
        }),
      ],
    }),
    
    // Reference
    defineField({
      name: 'author',
      type: 'reference',
      title: 'Author',
      to: [{type: 'author'}],
    }),
    
    // Array of references
    defineField({
      name: 'relatedPosts',
      type: 'array',
      title: 'Related Posts',
      of: [
        {
          type: 'reference',
          to: [{type: 'post'}],
        },
      ],
    }),
  ],
})

Special types

defineType({
  name: 'post',
  type: 'document',
  fields: [
    // Image with hotspot
    defineField({
      name: 'coverImage',
      type: 'image',
      title: 'Cover Image',
      options: {
        hotspot: true,
      },
      fields: [
        defineField({
          name: 'alt',
          type: 'string',
          title: 'Alt Text',
        }),
        defineField({
          name: 'caption',
          type: 'string',
          title: 'Caption',
        }),
      ],
    }),
    
    // File
    defineField({
      name: 'attachment',
      type: 'file',
      title: 'Attachment',
    }),
    
    // Slug
    defineField({
      name: 'slug',
      type: 'slug',
      title: 'Slug',
      options: {
        source: 'title',
        maxLength: 96,
      },
    }),
    
    // URL
    defineField({
      name: 'website',
      type: 'url',
      title: 'Website',
    }),
  ],
})

Portable Text (block content)

Portable Text is Sanity’s rich text format:
defineField({
  name: 'content',
  type: 'array',
  title: 'Content',
  of: [
    {
      type: 'block',
      styles: [
        {title: 'Normal', value: 'normal'},
        {title: 'H1', value: 'h1'},
        {title: 'H2', value: 'h2'},
        {title: 'H3', value: 'h3'},
        {title: 'Quote', value: 'blockquote'},
      ],
      lists: [
        {title: 'Bullet', value: 'bullet'},
        {title: 'Numbered', value: 'number'},
      ],
      marks: {
        decorators: [
          {title: 'Strong', value: 'strong'},
          {title: 'Emphasis', value: 'em'},
          {title: 'Code', value: 'code'},
        ],
        annotations: [
          {
            name: 'link',
            type: 'object',
            title: 'Link',
            fields: [
              {
                name: 'href',
                type: 'url',
                title: 'URL',
              },
            ],
          },
        ],
      },
    },
    // Add custom block types
    {
      type: 'image',
      options: {hotspot: true},
    },
  ],
})

Reusable object types

Define reusable object types that can be used across documents:
// seo.ts
export const seoType = defineType({
  name: 'seo',
  type: 'object',
  title: 'SEO',
  fields: [
    defineField({
      name: 'metaTitle',
      type: 'string',
      title: 'Meta Title',
      validation: (Rule) => Rule.max(60),
    }),
    defineField({
      name: 'metaDescription',
      type: 'text',
      title: 'Meta Description',
      rows: 3,
      validation: (Rule) => Rule.max(160),
    }),
    defineField({
      name: 'ogImage',
      type: 'image',
      title: 'Open Graph Image',
    }),
  ],
})

// Use it in documents
defineType({
  name: 'post',
  type: 'document',
  fields: [
    // ...
    defineField({
      name: 'seo',
      type: 'seo',
      title: 'SEO',
    }),
  ],
})

Validation

Add validation rules to ensure data quality:
defineField({
  name: 'title',
  type: 'string',
  title: 'Title',
  validation: (Rule) => Rule.required().min(10).max(100),
})

defineField({
  name: 'email',
  type: 'string',
  title: 'Email',
  validation: (Rule) => Rule.required().email(),
})

defineField({
  name: 'slug',
  type: 'slug',
  title: 'Slug',
  validation: (Rule) => Rule.required().custom((slug) => {
    if (!slug?.current) return true
    const regex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
    if (!regex.test(slug.current)) {
      return 'Slug must be lowercase and use hyphens'
    }
    return true
  }),
})

defineField({
  name: 'content',
  type: 'array',
  of: [{type: 'block'}],
  validation: (Rule) => Rule.required().min(1),
})

Field options

Customize field behavior with options:
defineField({
  name: 'status',
  type: 'string',
  title: 'Status',
  options: {
    list: [
      {title: 'Draft', value: 'draft'},
      {title: 'Published', value: 'published'},
      {title: 'Archived', value: 'archived'},
    ],
    layout: 'radio', // or 'dropdown'
  },
})

defineField({
  name: 'featured',
  type: 'boolean',
  title: 'Featured',
  description: 'Display this post on the homepage',
  initialValue: false,
})

defineField({
  name: 'slug',
  type: 'slug',
  title: 'Slug',
  options: {
    source: 'title',
    maxLength: 96,
    slugify: (input) => input.toLowerCase().replace(/\s+/g, '-'),
  },
})

Conditional fields

Show/hide fields based on other field values:
defineType({
  name: 'post',
  type: 'document',
  fields: [
    defineField({
      name: 'hasVideo',
      type: 'boolean',
      title: 'Has Video',
    }),
    defineField({
      name: 'videoUrl',
      type: 'url',
      title: 'Video URL',
      hidden: ({document}) => !document?.hasVideo,
    }),
  ],
})

Read-only fields

defineField({
  name: 'createdAt',
  type: 'datetime',
  title: 'Created At',
  readOnly: true,
  initialValue: () => new Date().toISOString(),
})

Field groups

Organize fields into tabs:
defineType({
  name: 'post',
  type: 'document',
  groups: [
    {name: 'content', title: 'Content', default: true},
    {name: 'seo', title: 'SEO'},
    {name: 'settings', title: 'Settings'},
  ],
  fields: [
    defineField({
      name: 'title',
      type: 'string',
      title: 'Title',
      group: 'content',
    }),
    defineField({
      name: 'metaTitle',
      type: 'string',
      title: 'Meta Title',
      group: 'seo',
    }),
    defineField({
      name: 'featured',
      type: 'boolean',
      title: 'Featured',
      group: 'settings',
    }),
  ],
})

Preview configuration

Customize how documents appear in lists:
defineType({
  name: 'author',
  type: 'document',
  preview: {
    select: {
      title: 'name',
      subtitle: 'role',
      media: 'image',
    },
    prepare({title, subtitle, media}) {
      return {
        title,
        subtitle: subtitle ? `Role: ${subtitle}` : undefined,
        media,
      }
    },
  },
})

Initial values

Set default values for new documents:
defineType({
  name: 'post',
  type: 'document',
  initialValue: () => ({
    status: 'draft',
    publishedAt: new Date().toISOString(),
    featured: false,
  }),
})

Array member types

Define inline array item schemas:
import {defineArrayMember} from 'sanity'

defineField({
  name: 'sections',
  type: 'array',
  title: 'Page Sections',
  of: [
    defineArrayMember({
      name: 'hero',
      type: 'object',
      title: 'Hero Section',
      fields: [
        defineField({
          name: 'heading',
          type: 'string',
          title: 'Heading',
        }),
        defineField({
          name: 'image',
          type: 'image',
          title: 'Background Image',
        }),
      ],
      preview: {
        select: {title: 'heading', media: 'image'},
      },
    }),
    defineArrayMember({
      name: 'textBlock',
      type: 'object',
      title: 'Text Block',
      fields: [
        defineField({
          name: 'text',
          type: 'array',
          of: [{type: 'block'}],
        }),
      ],
    }),
  ],
})
Use defineType, defineField, and defineArrayMember helpers for better TypeScript inference and autocomplete.
Document type names must be unique across your entire schema. Object types can be reused across multiple documents.

Build docs developers (and LLMs) love