
Content Modeling Best Practices
- 3.3k installs
- 171 repo stars
- Updated July 29, 2026
- sanity-io/agent-toolkit
content-modeling-best-practices is an agent skill that guides structured headless CMS schema design, reuse patterns, reference versus embed choices, and taxonomy planning.
About
content-modeling-best-practices teaches structured content modeling for headless CMS projects, with Sanity-specific notes but principles that apply broadly. Core principles state content is data not pages, maintain a single source of truth, future-proof for channels that do not exist yet, and optimize for editors creating content. The skill applies when starting new projects, evaluating structured versus free-form content, choosing references versus embedded objects, planning multi-channel delivery, or refactoring schemas that are too page-shaped or presentation-driven. Reference topics include separation of concerns between content and presentation, reference versus embedding tradeoffs, content reuse patterns across the reuse spectrum, and flat hierarchical or faceted taxonomy classification. Developers use it to decide field shapes, debate reusable versus nested content, plan omnichannel models, and review whether a schema is presentation-driven. The references/ directory loads focused guidance per modeling decision instead of one monolithic document.
- Four core principles: content as data, single source of truth, future-proofing, editor-centric design.
- Applies when designing schemas, choosing references versus embeds, or refactoring page-shaped models.
- references/ covers separation of concerns, reference vs embedding, reuse, and taxonomy classification.
- Targets omnichannel headless CMS design with Sanity implementation notes where relevant.
- Loads topic-specific reference files instead of dumping every modeling guide at once.
Content Modeling Best Practices by the numbers
- 3,272 all-time installs (skills.sh)
- +118 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #119 of 1,879 Documentation skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 2, 2026 (Skillselion catalog sync)
content-modeling-best-practices capabilities & compatibility
- Capabilities
- separation of content and presentation guidance · reference versus embedding decision framework · content reuse pattern catalog · taxonomy classification references
- Use cases
- documentation · planning
What content-modeling-best-practices says it does
Deciding between references and embedded content
references/reference-vs-embedding.md
npx skills add https://github.com/sanity-io/agent-toolkit --skill content-modeling-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3.3k |
|---|---|
| repo stars | ★ 171 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 29, 2026 |
| Repository | sanity-io/agent-toolkit ↗ |
How should I model structured content for a headless CMS without baking presentation into the schema?
Design structured headless CMS schemas with separation of concerns, reference versus embed decisions, reuse patterns, and taxonomies.
Who is it for?
Developers designing or refactoring Sanity or other headless CMS content types and field shapes.
Skip if: Skip when you only need GROQ query syntax or frontend rendering without schema architecture decisions.
When should I use this skill?
User debates references versus embedded objects, plans omnichannel content models, or reviews page-shaped schemas.
What you get
Clear modeling principles plus reference guidance on separation of concerns, references, reuse, and taxonomies for schema design.
- Schema modeling principles
- Topic-specific modeling references
Files
Content Modeling Best Practices
Principles for designing structured content that's flexible, reusable, and maintainable. These concepts apply to any headless CMS but include Sanity-specific implementation notes.
When to Apply
Reference these guidelines when:
- Starting a new project and designing the content model
- Evaluating whether content should be structured or free-form
- Deciding between references and embedded content
- Planning for multi-channel content delivery
- Refactoring existing content structures
Core Principles
1. Content is data, not pages — Structure content for meaning, not presentation 2. Single source of truth — Avoid content duplication 3. Future-proof — Design for channels that don't exist yet 4. Editor-centric — Optimize for the people creating content
References
Start with the reference that matches the modeling decision in front of you, instead of loading every topic at once. See references/ for detailed guidance on specific topics:
references/separation-of-concerns.md— Separating content from presentationreferences/reference-vs-embedding.md— When to use references vs embedded objectsreferences/content-reuse.md— Content reuse patterns and the reuse spectrumreferences/taxonomy-classification.md— Flat, hierarchical, and faceted classification
Content Reuse Patterns
Effective content models maximize reuse while minimizing duplication. Here are patterns for achieving both.
The Content Reuse Spectrum
Full Duplication ←————————————————→ Full Reference
(Copy everything) (Link to one source)Most real-world content sits somewhere in between.
Pattern 1: Shared Components
Create reusable content blocks that can be embedded anywhere.
Use case: Testimonials, FAQs, CTAs that appear on multiple pages.
// Standalone testimonial documents
defineType({
name: 'testimonial',
type: 'document',
fields: [
defineField({ name: 'quote', type: 'text' }),
defineField({ name: 'author', type: 'string' }),
defineField({ name: 'company', type: 'string' }),
]
})
// Reference in page builders
defineField({
name: 'pageBuilder',
type: 'array',
of: [
{ type: 'reference', to: [{ type: 'testimonial' }] }
]
})Pattern 2: Shared Field Sets
Extract common fields into reusable definitions.
Use case: SEO fields, social metadata, common dates.
// Shared field definition
export const seoFields = [
defineField({ name: 'seoTitle', type: 'string' }),
defineField({ name: 'seoDescription', type: 'text' }),
defineField({ name: 'ogImage', type: 'image' }),
]
// Spread into multiple types
defineType({
name: 'page',
fields: [
defineField({ name: 'title', type: 'string' }),
...seoFields
]
})
defineType({
name: 'post',
fields: [
defineField({ name: 'title', type: 'string' }),
...seoFields
]
})Pattern 3: Taxonomy References
Centralize classification for consistent tagging.
Use case: Categories, tags, topics that span content types.
// Central taxonomy
defineType({
name: 'category',
type: 'document',
fields: [
defineField({ name: 'title', type: 'string' }),
defineField({ name: 'slug', type: 'slug' }),
]
})
// Used across content types
defineField({
name: 'categories',
type: 'array',
of: [{ type: 'reference', to: [{ type: 'category' }] }]
})Pattern 4: Content Fragments
Small, reusable pieces that combine into larger content.
Use case: Bios, addresses, contact info.
// Fragment type
defineType({
name: 'contactInfo',
type: 'object',
fields: [
defineField({ name: 'email', type: 'email' }),
defineField({ name: 'phone', type: 'string' }),
defineField({ name: 'address', type: 'text' }),
]
})
// Reused across types
defineType({
name: 'office',
fields: [
defineField({ name: 'name', type: 'string' }),
defineField({ name: 'contact', type: 'contactInfo' }),
]
})Anti-Pattern: Over-Abstraction
Not everything needs to be reusable. If content is only used in one place, embedding is simpler.
Signs of over-abstraction:
- References that are only used once
- Editors navigating multiple documents for one page
- Complex queries joining rarely-shared content
Reference vs Embedding Content
When should content be linked (referenced) vs copied (embedded)? This decision affects reusability, query complexity, and editing workflows.
The Trade-offs
| Aspect | Reference | Embedded Object |
|---|---|---|
| Reusability | ✅ Shared across documents | ❌ Copied per document |
| Single source | ✅ Update once, reflects everywhere | ❌ Must update each copy |
| Query complexity | Requires joins/expansion | Inline, simpler queries |
| Editing UX | Separate editing interface | All fields in one place |
| Independence | Can exist on its own | Only exists within parent |
When to Reference
Use references when content:
- Is reusable — Same author across many articles
- Needs central management — Update product info once
- Has its own lifecycle — Published/draft independent of parent
- Should stay in sync — Price changes reflect everywhere
Examples:
- Author profiles
- Product catalog items
- Shared testimonials
- Category taxonomy
- Reusable CTAs
When to Embed
Use embedded objects when content:
- Is unique to this document — Page-specific hero
- Doesn't make sense alone — SEO metadata
- Should be copied, not linked — Historical snapshot
- Simplifies editing — All fields in one form
Examples:
- SEO metadata
- Page-specific sections
- Address information
- Social links
- Configuration options
Sanity Implementation
// Reference: Author is reusable
defineField({
name: 'author',
type: 'reference',
to: [{ type: 'author' }]
})
// Embedded: SEO is page-specific
defineField({
name: 'seo',
type: 'object',
fields: [
defineField({ name: 'title', type: 'string' }),
defineField({ name: 'description', type: 'text' })
]
})The Hybrid Approach
Sometimes you want both: a reference for the canonical data, plus embedded overrides.
defineField({
name: 'featuredProduct',
type: 'object',
fields: [
defineField({
name: 'product',
type: 'reference',
to: [{ type: 'product' }]
}),
defineField({
name: 'overrideTitle',
type: 'string',
description: 'Optional: Override the product title for this context'
}),
]
})Query uses coalesce(overrideTitle, product->title).
Separation of Content and Presentation
The most important principle in structured content: separate what content IS from how it LOOKS.
The Problem
When content is tied to presentation:
- Redesigns require content migration
- Content can't be reused across channels (web, mobile, voice)
- Editors make design decisions instead of content decisions
- A/B testing requires duplicate content
The Principle
Model content based on meaning and purpose, not visual appearance.
Bad: Presentation-Focused
BigHeroText → What if we want small heroes?
RedButton → What if brand colors change?
ThreeColumnLayout → What if mobile needs one column?
LeftSidebar → Position is a frontend concern
MobileImage → Device-specific content is fragileGood: Meaning-Focused
Headline → The main message (render however)
CallToAction → An action we want users to take
Features → A list of things (columns decided by frontend)
RelatedContent → Content relationships (position by context)
Image → One image with responsive cropsTesting Your Model
Ask: "If we completely redesigned the site, would these field names still make sense?"
threeColumnFeatures→ ❌ Fails (what if 2 columns?)features→ ✅ Works (describes the content's purpose: a list of product features)blueHighlightBox→ ❌ Fails (what if we go purple?)callout→ ✅ Works (describes the content's role: an attention-grabbing aside)
Sanity Implementation
// ❌ Avoid presentation-focused names
defineField({ name: 'bigHeroText', type: 'string' })
defineField({ name: 'fontSize', type: 'number' })
defineField({ name: 'backgroundColor', type: 'color' })
// ✅ Use meaning-focused names
defineField({ name: 'headline', type: 'string' })
defineField({ name: 'emphasis', type: 'string', options: { list: ['standard', 'prominent'] } })
defineField({ name: 'tone', type: 'string', options: { list: ['neutral', 'warning', 'success'] } })The frontend translates tone: 'warning' to visual styles. Content stays semantic.
Taxonomy and Classification
Organizing content with taxonomies enables filtering, navigation, and content relationships. Well-designed taxonomies scale; poorly designed ones become maintenance nightmares.
Types of Classification
Flat Taxonomy
Simple list of terms with no hierarchy.
Use for: Tags, simple categories Example: Blog tags: "javascript", "react", "tutorial"
defineType({
name: 'tag',
type: 'document',
fields: [
defineField({ name: 'title', type: 'string' }),
defineField({ name: 'slug', type: 'slug' }),
]
})Hierarchical Taxonomy
Terms with parent-child relationships.
Use for: Product categories, content sections Example: Electronics > Phones > Smartphones
defineType({
name: 'category',
type: 'document',
fields: [
defineField({ name: 'title', type: 'string' }),
defineField({ name: 'slug', type: 'slug' }),
defineField({
name: 'parent',
type: 'reference',
to: [{ type: 'category' }],
description: 'Parent category (leave empty for top-level)'
}),
]
})Faceted Classification
Multiple independent dimensions.
Use for: Complex filtering (e-commerce) Example: Filter by color AND size AND price range
// Multiple taxonomy types
defineField({ name: 'color', type: 'reference', to: [{ type: 'color' }] })
defineField({ name: 'size', type: 'reference', to: [{ type: 'size' }] })
defineField({ name: 'material', type: 'reference', to: [{ type: 'material' }] })Design Principles
1. Mutual Exclusivity (When Appropriate)
Categories should be distinct. If items frequently belong to multiple categories, consider tags instead.
Categories: One primary classification Tags: Many optional classifications
2. User-Centric Naming
Use terms your audience uses, not internal jargon.
Bad: "Content Assets" (internal term) Good: "Resources" or "Downloads" (user term)
3. Balanced Depth
Too shallow: Everything lumped together Too deep: Users can't find anything
Rule of thumb: 3-4 levels max for hierarchies
4. Scalable Structure
Design for 10x growth. Will your structure work with 10,000 items?
Querying Taxonomies
Get all items in a category
*[_type == "product" && category._ref == $categoryId]Get items in category OR children
// First get all descendant category IDs
*[_type == "product" && category._ref in
*[_type == "category" && (
_id == $categoryId ||
parent._ref == $categoryId ||
parent->parent._ref == $categoryId
)]._id
]Get category tree
*[_type == "category" && !defined(parent)]{
title,
slug,
"children": *[_type == "category" && parent._ref == ^._id]{
title,
slug,
"children": *[_type == "category" && parent._ref == ^._id]{
title,
slug
}
}
}Common Mistakes
Over-categorization
Creating a category for everything results in mostly-empty categories.
Fix: Start minimal, add categories as content grows.
Inconsistent Granularity
Some categories broad ("Technology"), others narrow ("React 18 Server Components").
Fix: Define clear criteria for category creation.
No Governance
Anyone can create taxonomy terms, leading to duplicates and inconsistency.
Fix: Limit who can create/edit taxonomy documents. Use validation.
Related skills
How it compares
CMS content architecture guidance, not a frontend rendering or GROQ query cheat sheet.
FAQ
What principles does content-modeling-best-practices teach?
Content is data not pages, single source of truth, future-proof channel design, and editor-centric modeling.
When should I load the reference guides?
When deciding field shapes, reference versus embed tradeoffs, reuse patterns, or taxonomy classification for a CMS schema.
Is Content Modeling Best Practices safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.