Recommended content model for nested geographic URL hierarchy (country/state/county/city)
Great question! For your hierarchical URL structure like country/state/county/city, you're on the right track thinking about references. Let me walk you through the most recommended approaches.
The Reference-Based Approach
For your hierarchy, the most flexible approach is to create separate document types for each level and connect them with reference fields. You have two main options:
1. Bottom-Up References (Recommended for Most Cases)
Each document references its parent:
// city.js
{
name: 'city',
type: 'document',
fields: [
{name: 'name', type: 'string'},
{name: 'slug', type: 'slug'},
{
name: 'county',
type: 'reference',
to: [{type: 'county'}]
}
]
}
// county.js references state, state references country, etc.Pros:
- Reference field appears where editors naturally expect it (in the child document)
- Easy to create new cities and assign them to counties
- Simpler editorial workflow
2. Top-Down References
Each document contains arrays of references to its children:
// country.js
{
name: 'country',
type: 'document',
fields: [
{name: 'name', type: 'string'},
{name: 'slug', type: 'slug'},
{
name: 'states',
type: 'array',
of: [{type: 'reference', to: [{type: 'state'}]}]
}
]
}Pros:
- Easier to see the full hierarchy from the top
- Good if you need to control order of children
Building Dynamic URLs
Regardless of which direction you choose, you can build full paths using GROQ queries. For a bottom-up structure:
*[_type == "city" && slug.current == $slug][0] {
name,
"path": slug.current,
county-> {
name,
"path": slug.current,
state-> {
name,
"path": slug.current,
country-> {
name,
"path": slug.current
}
}
}
}The -> operator follows references, and because references are bidirectional when queried, you can traverse them from either direction.
Important Consideration
An important thing to know: references are only visible in the Studio UI where the reference field is defined. So if you use bottom-up references, the city document will show the county reference field, but the county document won't show which cities reference it in the UI (though you can still query this relationship using the references() function).
Skip the Singleton
You mentioned using a singleton document to manage the hierarchy. This is usually unnecessary and adds complexity. The reference structure itself creates the hierarchy, and you can query it dynamically. A singleton would only be useful if you need to manually curate or order the hierarchy in a specific way.
However, if you need to create navigation menus that differ from your content structure, the Table of Contents pattern can be useful. This lets you provide different hierarchical views for different audiences without changing your core content structure.
Alternative: Path-Based Slugs
For simpler cases, you could also store the full path in each document's slug (e.g., usa-georgia-fulton-atlanta) and use string manipulation in your frontend. This is less flexible but might work if your hierarchy is relatively flat and doesn't change often.
The reference-based approach gives you the most flexibility and aligns well with Sanity's graph-like content organization, making it the recommended pattern for your use case!
Show original thread5 replies
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.