How to replace an image referenced hundreds of times in Sanity?
Yes, you can definitely replace an image that's referenced hundreds of times! Since the image asset is stored separately from your content documents (which only reference it), you have a couple of approaches:
Best Approach: Use a Migration Script
The most reliable way is to upload your new image and then use Sanity's migration tooling to update all references across your dataset. This ensures all documents are updated consistently.
Here's how to do it:
1. First, upload your new image (either through Studio or programmatically) and note its asset ID.
2. Create a migration script:
npx sanity migration create replaceImageAsset3. Write the migration to find and replace all references:
import {defineMigration, at, set} from 'sanity/migrate'
const OLD_ASSET_ID = 'image-abc123...' // Your old image asset ID
const NEW_ASSET_ID = 'image-xyz789...' // Your new image asset ID
export default defineMigration({
title: 'Replace image asset across all documents',
migrate: {
document(doc, context) {
const mutations = []
// Helper function to recursively find and replace asset references
function findAndReplaceAsset(obj: any, path: string[] = []) {
if (!obj || typeof obj !== 'object') return
// Check if this is an asset reference
if (obj._ref === OLD_ASSET_ID) {
mutations.push(at([...path, '_ref'], set(NEW_ASSET_ID)))
}
// Recursively check nested objects and arrays
for (const [key, value] of Object.entries(obj)) {
if (key.startsWith('_')) continue // Skip system fields
findAndReplaceAsset(value, [...path, key])
}
}
findAndReplaceAsset(doc)
return mutations
}
}
})4. Test with dry-run first:
npx sanity migration run replaceImageAsset --dry-run5. Execute the migration:
npx sanity migration run replaceImageAssetAlternative: Target Specific Fields
If you know exactly which fields contain the image (like mainImage or thumbnail), you can make the migration more targeted:
export default defineMigration({
title: 'Replace specific image field',
documentTypes: ['post', 'page'], // Only run on certain document types
migrate: {
document(doc, context) {
const mutations = []
// Check mainImage field
if (doc.mainImage?.asset?._ref === OLD_ASSET_ID) {
mutations.push(at('mainImage.asset._ref', set(NEW_ASSET_ID)))
}
// Check thumbnail field
if (doc.thumbnail?.asset?._ref === OLD_ASSET_ID) {
mutations.push(at('thumbnail.asset._ref', set(NEW_ASSET_ID)))
}
return mutations
}
}
})Why Not Delete and Re-upload?
You might wonder if you can just delete the old asset and upload a new one with the same ID. Unfortunately, Sanity's asset IDs are system-generated based on the file content (using SHA-1 hashing), so you can't specify custom asset IDs during upload. The migration approach is the supported way to handle this.
Additional Tips
- The migration will handle all references automatically, even in nested structures
- Run it with
--dry-runfirst to see what will change - The old asset will remain in your dataset until you manually delete it (references don't prevent deletion)
- Consider using GROQ to find all documents referencing the old asset first:
*[references("image-abc123...")]
This approach is reliable, reversible (you can write another migration to switch back), and handles edge cases like nested references automatically.
Show original thread8 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.