Hierarchy
Sanity’s hierarchy primitive lets you organise documents into a tree, similar to a file system.
Sanity’s hierarchy primitive is built around three document types: sanity.tree, sanity.directory, and sanity.symlink, and a single parent reference field that any document can carry.
Public beta
The hierarchy primitive is in public beta. The shape of the documents and the validation rules described on this page may change before general availability. The first product built on top of the primitive is folders in Media Library.
Document types
sanity.tree
The root of your library’s hierarchy. Every library has one tree, created on demand the first time you create a folder.
sanity.directory
A folder. Has a
nameand aparentreference to its containing tree or directory.Required fields:
parentsanity.symlink
A shortcut. Makes an asset appear inside a folder in addition to its primary location.
Required fields:
parent,target
How the data model works
Each document in the hierarchy stores only its direct parent via the parent reference field. There is no children list; children are discovered by querying:
*[parent._ref == $parentId]
To walk upward from a document to the root, fetch its parent._ref and repeat until you reach a sanity.tree.
The parent field can appear on any document type that you want to participate in the hierarchy. In Media Library, for example, sanity.asset and sanity.asset.collection both carry parent to place themselves inside folders.
Creating the tree
A hierarchy starts with a single sanity.tree. Use createIfNotExists with a deterministic _id so multiple writers can call this safely without producing duplicate trees.
await client.createIfNotExists({
_id: 'root-tree',
_type: 'sanity.tree',
})curl -X POST "https://api.sanity.io/v2025-02-19/{mutate-endpoint}" \
-H "Authorization: Bearer ${SANITY_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"mutations": [
{"createIfNotExists": {"_id": "root-tree", "_type": "sanity.tree"}}
]
}'Consuming applications may require a specific ID
For example, the Media Library requires the tree’s _id to be tree.{libraryId}
Creating directories
A sanity.directory carries a name (string) and a parent reference. Top-level directories point at the tree; nested directories point at another directory.
const tree = await client.fetch(`*[_type == "sanity.tree"][0]{_id}`)
const topLevel = await client.create({
_type: 'sanity.directory',
name: 'Top Level',
parent: {_ref: tree._id},
})
await client.create({
_type: 'sanity.directory',
name: 'Nested',
parent: {_ref: topLevel._id},
})Querying the hierarchy
The hierarchy is queried using standard GROQ.
Direct children of a parent:
*[parent._ref == $parentId]
All directories:
*[_type == "sanity.directory"]
Sibling documents (same parent):
*[parent._ref == $parentId && _id != $currentId]
Walking ancestors is done client-side. Fetch a document’s parent and repeat until the type is sanity.tree. Depth is bounded by the maximum hierarchy depth.
Moving directories
Patch parent to point at the new parent. The server validates the destination before applying any change.
await client
.patch(directoryId)
.set({parent: {_ref: newParentId}})
.commit()If the new parent would create a cycle, exceed the maximum depth, or point at an unsupported type, the mutation fails. See the error reference.
Symlinks
A sanity.symlink is a pointer document. It has a parent (the folder it appears in) and a target (the document it refers to). It does not constrain the type of the target document.
await client.create({
_type: 'sanity.symlink',
parent: {_ref: destinationFolderId},
target: {_ref: targetDocumentId},
})Consuming applications may further constrain valid target types. For example, Media Library shortcuts only support sanity.asset targets and will not render correctly if target references a sanity.directory.
Deleting
Documents in the hierarchy are deleted with the standard delete mutation.
await client.delete(directoryId)
No cascade delete
Deleting a sanity.directory does not cascade to its children. The server rejects any delete where another document still references this directory via parent. Remove or reparent the children before deleting the parent, or include them in the same transaction.
Error reference
circular reference detected:
The mutation would create a cycle (for example, A → B → A).
Choose a differentparent.hierarchy depth exceeds maximum:
The mutation would push a document past the depth limit.
Flatten the structure or reparent under a shallower ancestor.parent must be a directory or a tree:parentresolves to a document that is not asanity.treeorsanity.directory.
Correct theparentvalue.parent reference cannot be weak:parentincludes_weak: true.
Remove the_weakflag.