# Hierarchy

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.

> [!WARNING]
> 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](https://www.sanity.io/docs/media-library/folders).

## Document types

#### Properties

| Property | Description |
| --- | --- |
| 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:

```groq
*[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.

**@sanity/client**

```typescript
await client.createIfNotExists({
  _id: 'root-tree',
  _type: 'sanity.tree',
})
```

**HTTP**

```sh
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"}}
    ]
  }'
```

> [!NOTE]
> Consuming applications may require a specific ID
> For example, the [Media Library](https://www.sanity.io/docs/media-library/folders) 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.

```typescript
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](https://www.sanity.io/docs/groq).

Direct children of a parent:

```groq
*[parent._ref == $parentId]
```

All directories:

```groq
*[_type == "sanity.directory"]
```

Sibling documents (same parent):

```groq
*[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](https://www.sanity.io/docs/content-lake/technical-limits).

## Moving directories

Patch `parent` to point at the new parent. The server validates the destination before applying any change.

```typescript
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](https://www.sanity.io/docs/content-lake/hierarchy).

## 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.

```typescript
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](https://www.sanity.io/docs/media-library/folders) 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.

```typescript
await client.delete(directoryId)
```

> [!WARNING]
> 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 different `parent`.
- `hierarchy depth exceeds maximum`: 
The mutation would push a document past the [depth limit](https://www.sanity.io/docs/content-lake/technical-limits). 
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.

