> For AI agents: the complete Sanity documentation index is available at [https://www.sanity.io/docs/llms.txt](https://www.sanity.io/docs/llms.txt).

# Update an asset to a new version with the API

Upload a new version of a Media Library asset, set it as current, and update the documents that use it with the HTTP API.

[Asset versions](https://www.sanity.io/docs/media-library/asset-versions) let you replace the file behind a Media Library asset, such as a retouched photo or an updated PDF, without creating a new asset. In the Media Library app, uploading a version, making it current, and updating the documents that use it takes a few clicks. With the HTTP API, each of those is a separate request, and uploading a version doesn't make it current.

This guide explains how to upload a new version of an existing asset, set it as the current version, and point the documents in your project datasets at it. Use it when you sync assets from an external DAM or another automated pipeline.

Prerequisites:

- Node.js v21.0 or later to run the built-in `fetch` API, or a compatible request library.
- A personal authentication token with read and write access to your organization's Media Library and to the project datasets that use the asset. The examples read it from the `SANITY_API_TOKEN` environment variable.
- Your library's `mediaLibraryId`, and the asset ID (the `_id` of the asset's `sanity.asset` document). To look up asset IDs, see [Link assets to documents](https://www.sanity.io/docs/media-library/link-media-assets).
- An API version of `2025-02-19` or later. The examples use `v2025-02-19` in their request URLs.

## Upload the new version

To add a version to an existing asset, upload the file to the `/media-libraries/MEDIA_LIBRARY_ID/upload` endpoint and pass the asset's ID in the `assetId` query parameter. In the following code, replace `MEDIA_LIBRARY_ID` with your library's ID and `ASSET_ID` with the asset ID.

**upload-version.ts**

```typescript
import fs from 'node:fs'

const mediaLibraryId = 'MEDIA_LIBRARY_ID'
const assetId = 'ASSET_ID'
const token = process.env.SANITY_API_TOKEN

const file = fs.readFileSync('assets/spring-launch-promo-v2.jpg')

const params = new URLSearchParams({
  assetId,
  filename: 'spring-launch-promo-v2.jpg',
})

const response = await fetch(
  `https://api.sanity.io/v2025-02-19/media-libraries/${mediaLibraryId}/upload?${params}`,
  {
    method: 'POST',
    body: file,
    headers: {
      Authorization: `Bearer ${token}`,
    },
  },
)

if (!response.ok) {
  throw new Error(`Upload failed (${response.status}): ${await response.text()}`)
}

const {assetInstance} = await response.json()

// The new version's instance ID. The next steps use it as ASSET_INSTANCE_ID.
console.log(assetInstance._id)
```

The `filename` parameter sets the version's title in the versions panel. The response contains the new version as `assetInstance`. Save `assetInstance._id`, the asset instance ID, for the next steps.

The response's `asset` object shows the asset as it was before the upload. Its `versions` array and `currentVersion` reference don't include the new version yet.

### Troubleshoot version uploads

A version upload fails in these cases:

- The asset doesn't exist: the endpoint responds with `404 Not Found` and `asset not found`. Check the `assetId` value against the asset's `_id`.
- The file type doesn't match the asset's type. An image asset only accepts image versions.
- The request has an `aspects` part: the endpoint responds with `aspects cannot be combined with assetId; aspects only apply when creating a new asset`. Set aspect values on the asset with a mutation instead.
- The request sets a `cdnAccessPolicy` that differs from the asset's policy: the endpoint responds with `cdn access policy does not match existing asset`. Omit the parameter, and the version inherits the asset's policy.
- The file is identical to an existing version: the endpoint responds with `409 Conflict`, and `existingAssetId` in the response body holds the ID of the matching instance.

## Set the new version as current

Uploading a version adds it to the asset's `versions` array, but it doesn't change `currentVersion`. [Container URLs](https://www.sanity.io/docs/media-library/container-urls) and queries that resolve `currentVersion` keep serving the previous version until you update it.

To make the new version current, patch the asset's `currentVersion` reference with the Media Library `mutate` endpoint. In the following code, replace `ASSET_INSTANCE_ID` with the `assetInstance._id` value from the upload response.

**set-current-version.ts**

```typescript
const mediaLibraryId = 'MEDIA_LIBRARY_ID'
const assetId = 'ASSET_ID'
const assetInstanceId = 'ASSET_INSTANCE_ID'
const token = process.env.SANITY_API_TOKEN

const mutations = [
  {
    patch: {
      // Match the asset and its draft, if one exists.
      query: '*[_type == "sanity.asset" && (_id == $id || _id == "drafts." + $id)]',
      params: {id: assetId},
      set: {
        currentVersion: {_type: 'reference', _ref: assetInstanceId},
      },
    },
  },
]

const response = await fetch(
  `https://api.sanity.io/v2025-02-19/media-libraries/${mediaLibraryId}/mutate`,
  {
    method: 'POST',
    body: JSON.stringify({mutations}),
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  },
)

if (!response.ok) {
  throw new Error(`Mutation failed (${response.status}): ${await response.text()}`)
}
```

The patch also updates the asset's draft, if one exists, which matches what Media Library does when you select **Set as current**. After the change, container URLs serve the new version. The CDN can take a few minutes to reflect it.

> [!WARNING]
> Wait for video processing
> Video versions need to finish processing before you set them as current. Check that the video instance's `state` is `ready` before you patch `currentVersion`.

## Update documents that use the asset

Setting a new current version doesn't change your documents. Each image or file field references one specific version, so documents keep using the old version until you update them. Media Library marks these documents as outdated usage in the versions panel.

Updating documents takes two steps: link the new version to each dataset, then patch the documents to reference it.

### Link the new version to your dataset

Before a document can reference the new version, the version needs an asset document in the project dataset. Create it with the `assets/media-library-link` endpoint, once for each dataset that uses the asset. In the following code, replace `YOUR_PROJECT_ID` with your project ID and `production` with your dataset name.

**link-version.ts**

```typescript
const mediaLibraryId = 'MEDIA_LIBRARY_ID'
const assetId = 'ASSET_ID'
const assetInstanceId = 'ASSET_INSTANCE_ID'
const projectId = 'YOUR_PROJECT_ID'
const dataset = 'production'
const token = process.env.SANITY_API_TOKEN

const response = await fetch(
  `https://${projectId}.api.sanity.io/v2025-02-19/assets/media-library-link/${dataset}`,
  {
    method: 'POST',
    body: JSON.stringify({mediaLibraryId, assetId, assetInstanceId}),
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json',
    },
  },
)

if (!response.ok) {
  throw new Error(`Link failed (${response.status}): ${await response.text()}`)
}
```

The linked asset document has the same `_id` as the asset instance, so documents can reference `ASSET_INSTANCE_ID` directly.

> [!WARNING]
> Use a personal token to link assets
> The `assets/media-library-link` endpoint only accepts personal tokens that belong to a user. Robot tokens, including project API tokens and organization tokens, fail with `401 Unauthorized` and the message `Invalid non-global session for user id <id>`. If your pipeline uses a robot token for the other requests, use a personal token for this one.

### Point documents at the new version

To update the documents, list the asset's other versions from Media Library, find the documents that still reference one of them, and patch each document's `asset` reference. The following code updates documents that use the asset in a `poster` image field. Replace `poster` with the field name from your schema.

**sync-usage.ts**

```typescript
const mediaLibraryId = 'MEDIA_LIBRARY_ID'
const assetId = 'ASSET_ID'
const assetInstanceId = 'ASSET_INSTANCE_ID'
const projectId = 'YOUR_PROJECT_ID'
const dataset = 'production'
const token = process.env.SANITY_API_TOKEN

const headers = {
  'Authorization': `Bearer ${token}`,
  'Content-Type': 'application/json',
}

// 1. List the asset's other versions from Media Library.
const versionsResponse = await fetch(
  `https://api.sanity.io/v2025-02-19/media-libraries/${mediaLibraryId}/query`,
  {
    method: 'POST',
    body: JSON.stringify({
      query: '*[_id == $assetId][0].versions[instance._ref != $current].instance._ref',
      params: {assetId, current: assetInstanceId},
    }),
    headers,
  },
)
const {result: outdatedInstanceIds} = (await versionsResponse.json()) as {
  result: string[] | null
}

// 2. Find documents whose poster field still uses one of those versions.
const query = new URLSearchParams({
  query: '*[poster.asset._ref in $outdatedInstanceIds]{_id, _rev}',
  $outdatedInstanceIds: JSON.stringify(outdatedInstanceIds ?? []),
  perspective: 'raw',
})
const documentsResponse = await fetch(
  `https://${projectId}.api.sanity.io/v2025-02-19/data/query/${dataset}?${query}`,
  {headers},
)
const {result: documents} = (await documentsResponse.json()) as {
  result: {_id: string; _rev: string}[]
}

// 3. Point each document's asset reference at the current version.
const mutations = documents.map((document) => ({
  patch: {
    id: document._id,
    ifRevisionID: document._rev,
    set: {
      'poster.asset': {_type: 'reference', _ref: assetInstanceId},
    },
  },
}))

if (mutations.length > 0) {
  const response = await fetch(
    `https://${projectId}.api.sanity.io/v2025-02-19/data/mutate/${dataset}`,
    {
      method: 'POST',
      body: JSON.stringify({mutations}),
      headers,
    },
  )

  if (!response.ok) {
    throw new Error(`Mutation failed (${response.status}): ${await response.text()}`)
  }
}
```

Leave the field's `media` reference as it is. It points at the asset rather than a version, so it's the same for every version.

The `raw` perspective includes drafts and release versions, so the patches update them along with published documents, the same as **Sync all usage with current** in Media Library. Each patch sets `ifRevisionID`, so it fails if the document changed after the query. The patches run as one transaction, so if one fails, none apply. Run the script again to use the latest revisions.

To find documents that use an older version in any field, query `*[references($outdatedInstanceIds)]` instead, and check which field holds the asset before you patch each document.

> [!NOTE]
> Video fields
> Video fields don't have an asset document in the project dataset, so skip the link step. Their `asset` property is a global document reference whose `_ref` has the form `media-library:MEDIA_LIBRARY_ID:ASSET_INSTANCE_ID`. Match on that value when you query, and set `asset` to `{_type: 'globalDocumentReference', _ref: 'media-library:MEDIA_LIBRARY_ID:ASSET_INSTANCE_ID', _weak: true}` when you patch.

#### Related

[Asset versions](https://www.sanity.io/docs/media-library/asset-versions)
Manage asset versions in Media Library


[Link assets to documents](https://www.sanity.io/docs/media-library/link-media-assets)
Linking an asset in the Media Library to a document in your project requires a few extra steps.

[Media Library API reference](https://www.sanity.io/docs/http-reference/media-library)
HTTP endpoints reference for the Media Library API

