Creating Parent / Child relationships in Sanity goes beyond a parent reference field. In this guide we'll include initial value templates, filtered document lists and guides on how to effectively use and query documents that use these taxonomy schema.
Here's what we'll be building.
A category schema type with Parent documents
A list item in the Desk for each Parent to edit their Children and
Initial value templates to make sure every new Child document begins with its Parent reference pre-filled
Structure builder with parent/child taxonomy relationships. The "Compose" icon here will create a new "Category" document with "Liquorice" already set in the "parent" field.
Protip
If you're looking for an even fancier drag-and-drop Hierarchical Document List, this plugin has you covered!
Taxonomy schema
First, we'll need a schema for our taxonomy. We'll call ours category for this guide. You may call yours tag, section, or something else.
This guide will focus on building a simple, two-tier, parent/child hierarchy. But the ideas here could be extended further to deeper relationships.
A "Parent" Category is any category document that does not have the parent field defined.
A "Child" Category is any category document that with a parent field reference.
Add the below to your studio files, and register it to your Studio's schema.
// ./src/schema/category.jsimport{FiTag}from'react-icons/fi'exportdefault{
name:'category',
title:'Category',
type:'document',
icon: FiTag,
fields:[{name:'title', type:'string'},{
name:'parent',
type:'reference',
to:[{type:'category'}],// This ensures we cannot select other "children"
options:{
filter:'!defined(parent)',},},],// Customise the preview so parents are visualised in the studio
preview:{
select:{
title:'title',
subtitle:'parent.title',},prepare:({title, subtitle})=>({
title,
subtitle: subtitle ?`– ${subtitle}`:``,}),},}
Initial Value Templates
Before setting up the Desk Structure, make sure you have Initial Value Templates configured in the Studio.
With the right configuration, we can create Document Lists which show all Children of a specific Parent, and when creating a new document from that list pre-fill the parent reference field with that same Parent!
Here's we create a template called "Category: Child". Make sure this file is loaded from the parts in sanity.json also.
// ./src/initial-value-templates/index.jsimportTfrom'@sanity/base/initial-value-template-builder'exportdefault[T.template({
id:'category-child',
title:'Category: Child',
schemaType:'category',
parameters:[{name:`parentId`, title:`Parent ID`, type:`string`}],// This value will be passed-in from desk structurevalue:({parentId})=>({
parent:{_type:'reference', _ref: parentId},}),}),// Insert all your other Templates...T.defaults(),]
Setup Structure Builder
Desk Structure is a complex part of the Studio. The code we'll use here is no exception.
Below is a helper function to create structured parent/child lists of documents.
This could be enqueued into your Structure builder along with any other items like this:
The parentChild() helper function only accepts one parameter for now – the schema type – but you could extend it further for reuse by including parameters for Titles, Icons, etc.
This desk structure item is more dynamic than most. It will query the documentStore for all parent categories and create a S.listItem() for each one. Inside those, it will show all category documents with that parent as a reference.
// ./src/desk-structure/parent-child.jsimportSfrom'@sanity/desk-tool/structure-builder'import documentStore from'part:@sanity/base/datastore/document'import{map}from'rxjs/operators'import{FiTag}from'react-icons/fi'// You may need to customise your `views` array here for adding live preview iframes, incoming references, etcconst views =[S.view.form()]exportdefaultfunctionparentChild(schemaType ='category'){const categoryParents =`_type == "${schemaType}" && !defined(parent) && !(_id in path("drafts.**"))`returnS.listItem(schemaType).title('Categories').icon(FiTag).child(()=>
documentStore.listenQuery(`*[${categoryParents}]`).pipe(map((parents)=>S.list().title('All Categories').menuItems([S.menuItem().title('Add Category').icon(FiTag).intent({type:'create', params:{type: schemaType}}),]).items([S.listItem().title('Parent Categories').schemaType(schemaType).child(()=>S.documentList().schemaType(schemaType).title('Parent Categories').filter(categoryParents).canHandleIntent(()=>S.documentTypeList(schemaType).getCanHandleIntent()).child((id)=>S.document().documentId(id).views(views).schemaType(schemaType))),S.divider(),...parents.map((parent)=>S.listItem({
id: parent._id,
title: parent.title,
schemaType,child:()=>S.documentTypeList(schemaType).title('Child Categories').filter(`_type == "${schemaType}" && parent._ref == $parentId`).params({parentId: parent._id}).initialValueTemplates([S.initialValueTemplateItem('category-child',{
parentId: parent._id,}),]),})),]))))}
Note that accessing the documentStore directly like this is not common and on a larger dataset may produce undesirable results.
Pre-flight check
Now you should be able to view and edit a list of Parent documents, as well as clicking into Parents individually to see a list of Child documents.
Test the Initial Value Template by creating a new Category document while viewing a list of Children documents, the parent reference should be pre-filled.
Using taxonomy references
Consider when using these taxonomies to restrict references to Children.
For example in a schema of post, instead of an array of references where the author may add Parent and Child category references – have them select only "Child" documents.
Instead, using the select function in GROQ allows us to return a different value depending on a condition. In this case, whether a category has a parent field or not.
select works by returning whichever condition returns true first, and resolves the last item if nothing returns true.
The first condition defined(parent) will be true for any Child category. Otherwise, the fallback is the document's own slug.
In this guide, you’ll see how Sanity separates organizations, projects, datasets, and members by working through a hypothetical example of a growing company that can expand its content model as they grow – without needing a complete overhaul.