How to add specific fields to each page in a website built with Sanity, using arrays of objects or reserved IDs, and handling validation with custom rules.
I totally understand your challenge! Coming from WordPress with ACF, you're used to having page-specific fields. There are actually several approaches you can take in Sanity to solve this:
1. Conditional Fields (Simplest for minor variations)
If your pages mostly share the same fields but need a few specific additions, you can use conditional fields to show/hide fields based on a page type or template selector:
{
name: 'page',
type: 'document',
fields: [
{
name: 'title',
type: 'string'
},
{
name: 'pageType',
type: 'string',
options: {
list: [
{title: 'About', value: 'about'},
{title: 'Team', value: 'team'},
{title: 'Projects', value: 'projects'},
{title: 'Contact', value: 'contact'}
]
}
},
// Team-specific field
{
name: 'teamMembers',
type: 'array',
of: [{type: 'reference', to: [{type: 'person'}]}],
hidden: ({document}) => document?.pageType !== 'team'
},
// Contact-specific field
{
name: 'contactForm',
type: 'object',
hidden: ({document}) => document?.pageType !== 'contact'
}
]
}2. Page Builder / Modular Content Blocks (Most flexible)
This is the most popular approach and mirrors the ACF Flexible Content field. Instead of predefined fields, you give editors a toolkit of content blocks they can mix and match:
{
name: 'page',
type: 'document',
fields: [
{
name: 'title',
type: 'string'
},
{
name: 'content',
type: 'array',
of: [
{type: 'hero'},
{type: 'textBlock'},
{type: 'imageGallery'},
{type: 'teamGrid'},
{type: 'contactForm'},
{type: 'faqSection'}
]
}
]
}Each page can then compose its own layout. Your "Team" page might use [hero, textBlock, teamGrid] while "Contact" uses [hero, contactForm]. This gives you WordPress-like flexibility but with better structure.
3. Separate Document Types (Best for very different pages)
If your pages are fundamentally different, create separate schemas:
// schemas/aboutPage.js
export default {
name: 'aboutPage',
type: 'document',
fields: [
{name: 'title', type: 'string'},
{name: 'history', type: 'text'},
{name: 'timeline', type: 'array', of: [{type: 'milestone'}]}
]
}
// schemas/contactPage.js
export default {
name: 'contactPage',
type: 'document',
fields: [
{name: 'title', type: 'string'},
{name: 'email', type: 'string'},
{name: 'phone', type: 'string'}
]
}Then in your frontend routing, query based on the slug or path to determine which document type to fetch.
My Recommendation
For most sites, I'd go with option 2 (page builder with content blocks). It gives your editors the flexibility to create unique pages while maintaining consistency through reusable components. It's essentially what ACF's Flexible Content field does, but with better type safety and structure.
If you only need minor variations between pages, option 1 (conditional fields) is simpler and keeps everything in one schema.
Check out Sanity's page building course for a complete walkthrough of implementing the modular content block approach!
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.