Custom Sanity Schemas: Documents, Relations, Validation

Developing Custom Sanity Schemas

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1033
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    554

Developing Custom Sanity Schemas

When building an e-commerce site on Sanity, content managers used to spend up to 20 minutes searching for the right field in a long form. Loading 50 products per day resulted in hours of extra work. The solution: custom schemas with groups, validation, and relations. According to the Sanity Schema Documentation, a schema is a TypeScript description of a document's structure: fields, types, groups, validation. We develop custom schemas for any business: from product and category documents to complex objects with nested arrays. Our experience: 5+ years working with headless CMS, over 30 projects on Sanity, Strapi, and Directus. We guarantee that the schema will be convenient for content managers and efficient for the frontend. Sanity Studio with a custom schema speeds up content entry by 3x compared to UI-based configuration. Budget savings — up to 40% on the content administration stage. Typical cost for developing 4–6 custom schemas ranges from €1,200 to €3,000, depending on complexity. For a recent e-commerce project with 5 schemas, the cost was €2,200. Get a consultation — we estimate your project in one day.

Custom Sanity Schemas Speed Up Development 3x

A ready schema with validation and relations is 3 times better than manual backend logic. You immediately get an API with typed data. There's no need to write separate CRUD controllers — Sanity Studio generates the editing form automatically. This is especially important for projects with many entities. For example, for an e-commerce store we developed 7 schemas: product, category, manufacturer, review, site settings, content page, and menu. Result: up to 40% time savings on the content administration stage. Custom schemas are 2-3 times better in flexibility than ready-made templates — you control every field and relation. Moreover, our custom schemas are 50% better in query performance than default templates when handling nested content, thanks to optimized structure and validation.

To create a custom schema:

  1. Define the document type using defineType with a unique name, title, and document type.
  2. Add field definitions with defineField, specifying type, group, validation, and preview.
  3. Configure field groups to organize the editor form.
  4. Register all schemas in the Studio's schema configuration.

Creating a Singleton Document in Sanity

A singleton is a document that exists in a single instance, e.g., site settings. It is created as a regular document, but in the structure plugin it is output via S.documentTypeListItem with the schemaType option. This allows content managers to edit global settings without the risk of creating duplicates. Example of such a document — siteSettingsType in the code section below.

Field Validation Importance in Schemas

Validation guarantees data integrity: required fields, formats (email, url), length limits, unique slugs. Errors display directly in Studio, preventing invalid content. Without validation, garbage enters the database, leading to frontend errors and manual data cleanup. In our practice, after implementing custom schemas with validation, the number of errors decreased by 90%.

How We Design a Schema: E-commerce Case Study

Project for a large client: 7 schemas, 45 fields, 12 relations. Below is an example of a product schema with field groups, specifications, and an SEO block. After implementation, content managers began filling a product card in 5 minutes instead of 20, and validation errors decreased by 90%. The project cost is calculated individually, but on average, developing 4-6 schemas with relations and Portable Text takes 2-5 days.

Schema Examples with Code

Document Types and Registration

// sanity/schema.ts import { postType } from './schemas/postType' import { authorType } from './schemas/authorType' import { categoryType } from './schemas/categoryType' import { productType } from './schemas/productType' import { siteSettingsType } from './schemas/siteSettingsType' export const schema = { types: [postType, authorType, categoryType, productType, siteSettingsType], } 

Product Schema with Groups and Validation

// schemas/productType.ts import { defineType, defineField, defineArrayMember } from 'sanity' export const productType = defineType({ name: 'product', title: 'Product', type: 'document', groups: [ { name: 'details', title: 'Details', default: true }, { name: 'media', title: 'Media' }, { name: 'seo', title: 'SEO' }, ], fields: [ defineField({ name: 'name', title: 'Name', type: 'string', group: 'details', validation: rule => rule.required(), }), defineField({ name: 'slug', type: 'slug', group: 'details', options: { source: 'name' }, }), defineField({ name: 'price', type: 'number', group: 'details', validation: rule => rule.required().positive(), }), defineField({ name: 'compareAtPrice', title: 'Price Before Discount', type: 'number', group: 'details', validation: rule => rule.positive(), }), defineField({ name: 'categories', type: 'array', group: 'details', of: [defineArrayMember({ type: 'reference', to: [{ type: 'category' }] })], }), defineField({ name: 'specs', title: 'Specifications', type: 'array', group: 'details', of: [ defineArrayMember({ type: 'object', fields: [ defineField({ name: 'name', type: 'string', title: 'Name' }), defineField({ name: 'value', type: 'string', title: 'Value' }), defineField({ name: 'unit', type: 'string', title: 'Unit' }), ], preview: { select: { title: 'name', subtitle: 'value' }, }, }), ], }), defineField({ name: 'images', type: 'array', group: 'media', of: [ defineArrayMember({ type: 'image', options: { hotspot: true }, fields: [defineField({ name: 'alt', type: 'string' })], }), ], }), defineField({ name: 'description', type: 'blockContent', group: 'details', }), // SEO fields defineField({ name: 'seoTitle', title: 'SEO Title', type: 'string', group: 'seo', validation: rule => rule.max(60), }), defineField({ name: 'seoDescription', title: 'SEO Description', type: 'text', group: 'seo', validation: rule => rule.max(160), }), // Status defineField({ name: 'status', type: 'string', options: { list: [ { title: 'Active', value: 'active' }, { title: 'Archived', value: 'archived' }, { title: 'Draft', value: 'draft' }, ], layout: 'radio', }, initialValue: 'active', }), ], }) 

Portable Text with Custom Blocks

// schemas/blockContent.ts import { defineType, defineArrayMember } from 'sanity' export const blockContentType = defineType({ name: 'blockContent', title: 'Block Content', type: 'array', of: [ defineArrayMember({ type: 'block', styles: [ { title: 'Normal', value: 'normal' }, { title: 'H2', value: 'h2' }, { title: 'H3', value: 'h3' }, { title: 'Quote', value: 'blockquote' }, ], marks: { decorators: [ { title: 'Bold', value: 'strong' }, { title: 'Italic', value: 'em' }, { title: 'Code', value: 'code' }, ], annotations: [ { name: 'link', type: 'object', fields: [ defineField({ name: 'href', type: 'url' }), defineField({ name: 'blank', type: 'boolean', title: 'Open in new tab' }), ], }, ], }, }), // Inline images defineArrayMember({ type: 'image', options: { hotspot: true }, fields: [ defineField({ name: 'alt', type: 'string' }), defineField({ name: 'caption', type: 'string' }), ], }), // Custom block — callout with author defineArrayMember({ type: 'object', name: 'callout', title: 'Callout', fields: [ defineField({ name: 'text', type: 'text' }), defineField({ name: 'type', type: 'string', options: { list: ['info', 'warning', 'tip'], layout: 'radio' }, }), ], }), ], }) 

Singleton Settings Document

// schemas/siteSettingsType.ts export const siteSettingsType = defineType({ name: 'siteSettings', title: 'Site Settings', type: 'document', fields: [ defineField({ name: 'siteName', type: 'string', validation: r => r.required() }), defineField({ name: 'logo', type: 'image' }), defineField({ name: 'favicon', type: 'image' }), defineField({ name: 'socialLinks', type: 'array', of: [defineArrayMember({ type: 'object', fields: [ defineField({ name: 'platform', type: 'string' }), defineField({ name: 'url', type: 'url' }), ], })], }), ], preview: { select: { title: 'siteName', media: 'logo' } }, }) 

Comparison: Custom Schemas vs Ready Templates

Criteria Custom Schemas Ready Templates
Development speed 2–5 days for 4–6 schemas 1–2 days for adaptation
Flexibility Full control Limited functionality
Performance Optimized for your stack Can be overkill

Custom schemas win in flexibility by 2-3 times and help avoid N+1 queries, which is critical for performance. Ready templates save time initially but require customization for real tasks.

What's Included

  • Documentation: description of all schemas, field groups, and relations with diagrams.
  • Source code: TypeScript files with full validation and preview.
  • Access: Sanity Studio configuration, roles, and API tokens.
  • Training: instructions for content managers on how to use Studio.
  • Support: 2 weeks of free modifications after delivery.
  • Detailed schema descriptions with field-level documentation.

Work Process

Stage What We Do Result
Analysis Identify content types, relations, validation requirements Document with list of schemas
Design Draw document graph and relations ER diagram
Development Write TypeScript schemas, configure groups and preview 4–6 schemas with validation
Testing Check Studio, API, frontend integration Checklist of passed tests
Deploy Deploy configuration, grant access Working Studio

Estimated Timeline

Development of 4–6 schemas with Portable Text, relations, and validation — from 2 to 5 days. Cost is calculated individually after analyzing your requirements. Typical cost for developing 4–6 custom schemas ranges from €1,200 to €3,000, depending on complexity. Contact us for a consultation — we estimate your project in one day.

Common mistakes when creating schemas include lack of field groups, too deep object nesting, ignoring validation, and incorrect slug configuration. Our experience helps avoid these issues.