---
name: sanity-schema-design
description: Author Sanity schema the way an agency ships it to editors. Covers defineType/defineField conventions, schema icons, studio folder structure, page builder block organisation, reusable field templates with editor-facing descriptions, TypeScript type generation, and turning design screenshots into schema. Use when creating or reviewing Sanity schema types, building a page builder, or scaffolding a studio.
license: MIT
metadata:
  author: roboto-studio
  version: "1.0.0"
  updated: "2026-06-11"
  homepage: https://robotostudio.com/skills/sanity-schema-design
---

# Sanity schema design

Schema conventions for Sanity studios that editors actually enjoy using. The recurring theme: every field teaches the editor what it does, every type carries an icon, and the folder structure scales past the tenth page builder block without turning into a junk drawer.

## Basic structure

For TypeScript files, always import the Sanity helpers and use them everywhere: `defineType` around the whole type, `defineField` on every field, `defineArrayMember` only when needed.

```typescript
import { defineArrayMember, defineField, defineType } from "sanity";

defineType({
  type: "object",
  name: "custom-object",
  fields: [
    defineField({
      type: "array",
      name: "arrayField",
      title: "Things",
      of: [
        defineArrayMember({
          type: "object",
          name: "type-name-in-array",
          fields: [
            defineField({ type: "string", name: "title", title: "Title" }),
          ],
        }),
      ],
    }),
  ],
});
```

Always use named exports for schema types.

## Icons

Every document and block type gets an icon. Prefer `@sanity/icons` first; fall back to whichever icon set the project already has installed (`lucide-react` is common). An iconless type in a page builder insert menu is unfindable once the menu passes ten entries.

## Studio folder structure

Rough shape, with an index at each level so types compose into arrays near where they live:

```text
studio/
├── sanity.config.ts
├── structure.ts
├── components/
│   └── slug-field-component.tsx
├── plugins/
│   └── presentation-url.ts
├── schemaTypes/
│   ├── index.ts
│   ├── common.ts
│   ├── blocks/
│   │   ├── index.ts
│   │   ├── hero.ts
│   │   ├── cta.ts
│   │   ├── faq-accordion.ts
│   │   ├── feature-cards.ts
│   │   └── image-link-cards.ts
│   ├── definitions/
│   │   ├── index.ts
│   │   ├── button.ts
│   │   ├── custom-url.ts
│   │   ├── pagebuilder.ts
│   │   └── rich-text.ts
│   └── documents/
│       ├── author.ts
│       ├── blog.ts
│       ├── faq.ts
│       └── page.ts
└── utils/
    ├── og-fields.ts
    ├── seo-fields.ts
    └── slug.ts
```

Do not copy these names literally; the point is the layering. `documents/` for queryable document types, `blocks/` for page builder sections, `definitions/` for reusable field-level types.

### Block index pattern

Each folder's `index.ts` exports an array, so registering a new block is one import plus one array entry, and the root index stays small:

```typescript
import { callToAction } from "./call-to-action";
import { faqList } from "./faq-list";
import { featureCards } from "./feature-cards";
import { hero } from "./hero";
import { logoCloud } from "./logo-cloud";
import { pricingTable } from "./pricing-table";
import { richTextBlock } from "./rich-text-block";
import { statsCard } from "./stats-card";
import { testimonialQuote } from "./testimonial-quote";

export const pagebuilderBlocks = [
  hero,
  featureCards,
  faqList,
  callToAction,
  logoCloud,
  pricingTable,
  statsCard,
  testimonialQuote,
  richTextBlock,
];

export const blocks = [...pagebuilderBlocks];
```

## Common field templates

Every field gets a `name`, `title`, `description`, and `type`, with the description written for a non-technical editor and placed above `type`. These templates cover the fields almost every block needs:

### Eyebrow

```typescript
defineField({
  name: "eyebrow",
  title: "Eyebrow",
  description: "The smaller text that sits above the title to provide context",
  type: "string",
});
```

### Title

```typescript
defineField({
  name: "title",
  title: "Title",
  description: "The large text that is the primary focus of the block",
  type: "string",
});
```

### Heading level toggle

```typescript
defineField({
  name: "isHeadingOne",
  title: "Is it a <h1>?",
  type: "boolean",
  description:
    "By default the title is a <h2> tag. If you use this as the top block on the page, you can toggle this on to make it a <h1> instead",
  initialValue: false,
});
```

### Rich text

```typescript
defineField({
  name: "richText",
  title: "Rich Text",
  description:
    "Large body of text that has links, ordered/unordered lists and headings.",
  type: "richText",
});
```

### Buttons

```typescript
defineField({
  name: "buttons",
  title: "Buttons",
  description: "Add buttons here, the website will handle the styling",
  type: "array",
  of: [{ type: "button" }],
});
```

### Image

```typescript
defineField({
  name: "image",
  title: "Image",
  type: "image",
  fields: [
    defineField({
      name: "alt",
      type: "string",
      description:
        "Remember to use alt text for people to be able to read what is happening in the image if they are using a screen reader, it's also important for SEO",
      title: "Alt Text",
    }),
  ],
});
```

## Type generation

After adding or changing schema, regenerate TypeScript definitions:

```bash
sanity schema extract && sanity typegen generate --enforce-required-fields
```

Run this in the same change as the schema edit so the frontend types never drift.

## Building schema from screenshots

When asked to produce schema from a design screenshot, describe the types using the conventions above and lean on these visual cues:

- Tiny text above a title is likely an **eyebrow**
- Large unformatted text that reads as a header is a **title** or **subtitle**
- Text with formatting (bold, italics, lists) needs **richText**
- Every image gets an **alt text** field
- Repeated button patterns use the reusable button array
- If the project already defines `richTextField` or `buttonsField` helpers, use them

Always include editor-facing descriptions based on what the element does in the design.

## About this skill

Maintained by [Roboto Studio](https://robotostudio.com), a UK agency that has shipped Sanity studios for startups through enterprise. It distills the schema conventions we hold every project to. If you would rather have it done for you: [robotostudio.com/services/sanity](https://robotostudio.com/services/sanity).

Licensed MIT. Wow, I can't believe people are actually using these. Tell me if it worked: yo@robotostudio.com
