# Folders

> [!NOTE]
> Beta primitive
> Folders are built on the [hierarchy primitive in Content Lake](https://www.sanity.io/docs/content-lake/hierarchy), which is currently in public beta. The underlying API surface may change.

## Concepts

A folder hierarchy in Media Library is made up of three document types from the [Content Lake hierarchy primitive](https://www.sanity.io/docs/content-lake/hierarchy): `sanity.tree`, `sanity.directory`, and `sanity.symlink`.

Assets (`sanity.asset`) sit inside folders by carrying a `parent` reference to a `sanity.directory`.

## Getting started with the API

These examples use [@sanity/client](https://www.sanity.io/docs/apis-and-sdks/js-client-getting-started) configured against your Media Library. They also work via the standard Media Library [mutate](https://www.sanity.io/docs/http-reference/media-library) and [query](https://www.sanity.io/docs/http-reference/media-library) endpoints.

### Step 1: Create the tree

Every folder hierarchy starts with one `sanity.tree` document. 

**@sanity/client**

```typescript
import {createClient} from '@sanity/client'

const libraryId = '<your-library-id>'

const client = createClient({
  apiVersion: '2025-02-19',
  resource: {type: 'media-library', id: libraryId},
  token: process.env.SANITY_TOKEN,
})

await client.createIfNotExists({
  _id: `tree.${libraryId}`,
  _type: 'sanity.tree',
  name: 'folders',
})
```

**HTTP**

```ts
const libraryId = '<your-library-id>'
const token = '<personal-auth-token>'

await fetch(`https://api.sanity.io/v2025-02-19/media-libraries/${libraryId}/mutate`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${token}`,
  },
  body: JSON.stringify({
    mutations: [
      {
        createIfNotExists: {
          _id: `tree.${libraryId}`,
          _type: 'sanity.tree',
          name: 'folders',
        },
      },
    ],
  }),
})
```

> [!WARNING]
> Media Library requires the tree's `_id` to be `tree.{libraryId}`.

### Step 2: Create folders

Create a `sanity.directory` with a `name` and a `parent` reference. Top-level folders point at the tree; nested folders point at their parent directory.

```typescript
// Use the same `tree.${libraryId}` ID created in Step 1
const treeId = `tree.${libraryId}`

// Top-level folder
const marketing = await client.create({
  _type: 'sanity.directory',
  name: 'Marketing',
  parent: {_ref: treeId},
})

// Nested folder
const campaigns = await client.create({
  _type: 'sanity.directory',
  name: 'Campaigns',
  parent: {_ref: marketing._id},
})
```

For directory creation patterns, validation rules, and the error reference, see [Creating directories in the hierarchy primitive](https://www.sanity.io/docs/content-lake/hierarchy).

### Step 3: Place assets in a folder

For assets that are already uploaded, set `parent` on the `sanity.asset` document to move it into a folder.

```typescript
await client
  .patch(asset._id)
  .set({parent: {_ref: folderId}})
  .commit()
```

> [!TIP]
> Place assets inside a `sanity.directory` rather than directly under the tree. The schema permits both, but the Media Library app browses, lists, and operates on assets through directories. 
> Leave `parent` unset to keep an asset outside of the folder hierarchy.

## Upload directly into a folder

When uploading a new asset, set the `parent` query parameter on the upload endpoint to place it in a folder in a single request, no follow-up patch needed.

> [!TIP]
> `parent` must reference a `sanity.directory`. 
> `sanity.tree` is not a valid value, assets in Media Library cannot live directly under the tree, only inside a folder.

For the full upload pattern, see [Upload assets programmatically](https://www.sanity.io/docs/media-library/upload-assets).

## Querying assets and folders

Media Library hierarchy is queried with standard GROQ. The same patterns documented in [Querying the hierarchy](https://www.sanity.io/docs/content-lake/hierarchy) apply

You can query assets inside a specific folder with:

```groq
*[_type == "sanity.asset" && parent._ref == $folderId]
```

## Moving folders and assets

Patch `parent` to a new destination. The server validates the destination before any change is applied, see the [error reference](https://www.sanity.io/docs/content-lake/hierarchy).

```typescript
await client
  .patch(folderToMove._id)
  .set({parent: {_ref: newParentId}})
  .commit()
```

The same pattern applies to assets. Set `parent` to move an asset into a different folder, or `unset` it to remove the asset from any folder.

## Shortcuts

A **shortcut** makes a single asset visible inside an additional folder without duplicating the underlying file. Shortcuts are `sanity.symlink` documents that reference a destination folder via `parent` and an asset via `target`.

```typescript
await client.create({
  _type: 'sanity.symlink',
  parent: {_ref: destinationFolderId},
  target: {_ref: assetId},
})
```

> [!WARNING]
> Media Library constraint
> In Media Library, `target` must reference a `sanity.asset`. Targeting a `sanity.directory` (a “folder shortcut”) is not supported and will not render correctly in the app.

## Deleting folders

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

> [!WARNING]
> No cascade delete
> Deleting a folder does not cascade. The server rejects any delete where children still reference the folder via `parent`. See [deleting a hierarchy primitive](https://www.sanity.io/docs/content-lake/hierarchy).

In the Media Library app, the delete folder action handles this for you. It deletes children recursively and displays a confirmation dialog that surfaces information about contents and potential warnings. See [Deleting folders in the interface](https://www.sanity.io/docs/media-library/interface).

