
How to Create Your First Schema in Sanity
To create your first schema in Sanity, add a TypeScript file to your Studio's schemaTypes folder that exports a document type built with defineType, list its fields with defineField, then add the type to the array exported from schemaTypes/index.ts, which sanity.config.ts passes to schema.types. Run npm run dev, open Studio at http://localhost:3333, and your new document type appears in the sidebar, ready for editors to create content. A schema is simply a description of your content's shape: the document types, their fields and the rules each field follows.
Getting this first schema right sets the pattern for the rest of your project. In this guide you'll build a small blog model step by step: a post document with a title, slug, date, image and body, plus an author document that posts can reference. Along the way you'll learn how Sanity schemas are structured, how Studio turns them into forms, how to make list previews useful, and how to query your new content with GROQ.
What a Sanity Schema Is
A Sanity schema is JavaScript or TypeScript that tells Sanity Studio:
- Which document types exist: such as
post,authororproduct. - Which fields each type has: and what kind of data each field holds.
- How to present and validate them: labels, help text, input options and rules.
The Content Lake that stores your data doesn't enforce the schema. It stores whatever JSON you send it. The schema shapes the editing experience in Studio, which is why it lives alongside your Studio code. If you haven't set up a project yet, follow how to set up your first Sanity project first.
Where Schema Files Live
A new Studio created with npm create sanity@latest and the clean template has this structure:
my-studio/
├── schemaTypes/
│ └── index.ts
├── sanity.cli.ts
├── sanity.config.ts
├── package.json
└── tsconfig.json
The config imports the schema types and passes them to Studio:
// sanity.config.ts
import { defineConfig } from 'sanity';
import { structureTool } from 'sanity/structure';
import { visionTool } from '@sanity/vision';
import { schemaTypes } from './schemaTypes';
export default defineConfig({
name: 'default',
title: 'My Blog',
projectId: 'your-project-id',
dataset: 'production',
plugins: [structureTool(), visionTool()],
schema: {
types: schemaTypes,
},
});
And schemaTypes/index.ts starts as an empty array:
// schemaTypes/index.ts
export const schemaTypes = [];
The project structure guide explains the other files.
Step 1: Create a Document Type
Create schemaTypes/post.ts:
// schemaTypes/post.ts
import { defineField, defineType } from 'sanity';
export const post = defineType({
name: 'post',
title: 'Post',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
}),
],
});
Each part has a job:
- name: the identifier stored on every document as
_type. Use camelCase, and don't change it once content exists, because existing documents keep the old value. - title: the human-friendly label shown in Studio.
- type: 'document': makes this a top-level type editors can create, list and publish. Other types, such as
object, can only live inside documents. - fields: the inputs on the form.
defineType and defineField don't change what the schema does. They add type checking and autocomplete, so mistakes like a misspelled option are caught in your editor. The post on defineType and defineField goes into more detail.
Step 2: Register the Type
Add the new type to the array:
// schemaTypes/index.ts
import { post } from './post';
export const schemaTypes = [post];
Start the Studio:
npm run dev
Open http://localhost:3333, sign in, and you'll see Post in the content sidebar. Click it, create a new document, type a title and publish. You've just stored your first document in the Content Lake.
Step 3: Add More Fields
A blog post needs more than a title. Extend the fields array:
// schemaTypes/post.ts
import { defineArrayMember, defineField, defineType } from 'sanity';
export const post = defineType({
name: 'post',
title: 'Post',
type: 'document',
fields: [
defineField({
name: 'title',
title: 'Title',
type: 'string',
validation: (rule) => rule.required(),
}),
defineField({
name: 'slug',
title: 'Slug',
type: 'slug',
options: { source: 'title', maxLength: 96 },
validation: (rule) => rule.required(),
}),
defineField({
name: 'publishedAt',
title: 'Published at',
type: 'datetime',
initialValue: () => new Date().toISOString(),
}),
defineField({
name: 'excerpt',
title: 'Excerpt',
type: 'text',
rows: 3,
description: 'A short summary for listings and search results.',
}),
defineField({
name: 'mainImage',
title: 'Main image',
type: 'image',
options: { hotspot: true },
fields: [
defineField({ name: 'alt', title: 'Alternative text', type: 'string' }),
],
}),
defineField({
name: 'body',
title: 'Body',
type: 'array',
of: [defineArrayMember({ type: 'block' }), defineArrayMember({ type: 'image' })],
}),
],
});
What you've added:
| Field | Type | What editors get |
|---|---|---|
title | string | A single-line text input, required before publishing |
slug | slug | A URL-friendly value with a Generate button that uses the title |
publishedAt | datetime | A date and time picker, defaulting to now |
excerpt | text | A multi-line plain text box |
mainImage | image | An image upload with hotspot and crop, plus an alt text field |
body | array of block and image | A rich text editor that also accepts inline images |
Save the file and Studio reloads with the new fields. Existing documents simply show the new fields as empty, with no migration needed. For every available field type and option, see Sanity schema types.
Step 4: Add a Second Document Type
Posts usually have authors. Create schemaTypes/author.ts:
// schemaTypes/author.ts
import { defineField, defineType } from 'sanity';
export const author = defineType({
name: 'author',
title: 'Author',
type: 'document',
fields: [
defineField({
name: 'name',
title: 'Name',
type: 'string',
validation: (rule) => rule.required(),
}),
defineField({
name: 'image',
title: 'Image',
type: 'image',
options: { hotspot: true },
}),
defineField({
name: 'bio',
title: 'Bio',
type: 'text',
rows: 4,
}),
],
});
Register it:
// schemaTypes/index.ts
import { author } from './author';
import { post } from './post';
export const schemaTypes = [post, author];
Step 5: Link Posts to Authors
Add a reference field to post, after slug:
defineField({
name: 'author',
title: 'Author',
type: 'reference',
to: [{ type: 'author' }],
}),
Editors can now search for an existing author or create a new one from inside the post. In the stored document, the reference is just an ID:
{
"_type": "post",
"title": "Hello Sanity",
"author": { "_type": "reference", "_ref": "a1b2c3d4-..." }
}
References are a big topic, including weak references and filtered pickers, so they have their own guide: how to use references in Sanity schemas.
Step 6: Make the Document List Readable
By default, Studio shows each document's title in lists if it can find a title field. You can control this with a preview:
// in schemaTypes/post.ts, alongside fields
preview: {
select: {
title: 'title',
author: 'author.name',
media: 'mainImage',
},
prepare({ title, author, media }) {
return {
title,
subtitle: author ? `by ${author}` : 'No author',
media,
};
},
},
select can follow references with dot notation, as in author.name, and prepare turns the selected values into the title, subtitle and thumbnail shown in lists and reference pickers. It's a small touch that makes Studio much easier to use once you have dozens of documents.
Step 7: Query Your Content
With a couple of posts published, open the Vision tab in Studio and run:
*[_type == "post"] | order(publishedAt desc){
title,
"slug": slug.current,
publishedAt,
"author": author->name
}
You'll get something like:
[
{
"title": "Hello Sanity",
"slug": "hello-sanity",
"publishedAt": "2026-11-04T09:00:00.000Z",
"author": "Sajjad"
}
]
The -> operator follows the reference and pulls in the author's name. That's the same query your frontend will run with @sanity/client or next-sanity. To learn more, start with what is GROQ.
Common First-Schema Mistakes
- Forgetting to register the type: if a type doesn't appear in Studio, check it's in the
schemaTypesarray. - Duplicate names: every type name must be unique across the schema, including object types. Studio shows a schema error if two clash.
- Renaming fields with content: changing
namehides the old data in Studio, because the documents still store the old key. Add a new field and migrate instead. - Using
stringfor long text: usetextfor multi-line plain text and an array ofblockfor rich text. - Skipping descriptions: a short
descriptionon tricky fields saves editors a lot of guessing.
Once you have a few types, read content modelling best practices in Sanity to plan the rest of your model.
FAQ: Your First Sanity Schema
No. Schemas can be plain JavaScript, and the shape is identical. TypeScript is the default in new projects because defineType and defineField give you autocomplete and catch mistakes, so it's worth using even if the rest of your code is JavaScript.
A document type is a top-level item that editors create, list and publish on its own, with its own ID. An object type is a group of fields that only exists inside a document or another object, such as an address or an SEO block.
Not when you add fields, because the Content Lake is schemaless and new fields simply start empty. When you rename or remove a field, or change its type, existing documents keep the old data, so you'll need a migration script if you want to move it.
The most common cause is that the type isn't included in the schemaTypes array passed to schema.types in sanity.config.ts. Also check the terminal and browser console for schema errors, such as a duplicate type name or a missing type property.
Yes, and you should. The usual pattern is one file per type in schemaTypes, sometimes grouped into documents and objects folders, all imported into schemaTypes/index.ts. Sanity only cares about the final array of types.
Add a validation function to the field, such as validation: (rule) => rule.required(). Studio then blocks publishing until the field has a value. Validation only runs in Studio, not on API writes.
Conclusion
Creating your first schema in Sanity takes three moves: define a document type with defineType and defineField, add it to the array in schemaTypes/index.ts, and run the Studio to see the form it produces. From there you grow the model by adding fields, more document types and references between them, with previews to keep lists readable and GROQ to check what's stored.
Keep each type in its own file, choose field names carefully because they become your data's keys, and add validation and descriptions as you go. When you're ready to make fields stricter, continue with how to add validation rules to Sanity fields.


