Content Lake (Datastore)

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

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 name and a parent reference to its containing tree or directory.

    Required fields: parent

  • sanity.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.

Consuming applications may require a specific ID

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

Error reference

  • circular reference detected:
    The mutation would create a cycle (for example, A → B → A).
    Choose a different parent.
  • 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:
    parent resolves to a document that is not a sanity.tree or sanity.directory.
    Correct the parent value.
  • parent reference cannot be weak:
    parent includes _weak: true.
    Remove the _weak flag.

Was this page helpful?