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 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
fetchAPI, 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_TOKENenvironment variable. - Your library's
mediaLibraryId, and the asset ID (the_idof the asset'ssanity.assetdocument). To look up asset IDs, see Link assets to documents. - An API version of
2025-02-19or later. The examples usev2025-02-19in 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.
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 Foundandasset not found. Check theassetIdvalue 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
aspectspart: the endpoint responds withaspects 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
cdnAccessPolicythat differs from the asset's policy: the endpoint responds withcdn 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, andexistingAssetIdin 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 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.
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.
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.
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.
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.
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.
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.