
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeYou're absolutely right that patches can only be performed on top-level documents, not directly on nested child blocks. However, you can still modify nested blocks within a document using the patch API's path syntax with JSONMatch.
When you patch a document, you target specific blocks within the body array using path expressions with the block's _key. Here's how to change the _type of nested blocks:
// For each block you want to change
const patches = doc.body
.filter(block => block._type === 'ctaButton')
.map(block => ({
id: doc._id, // Patch the DOCUMENT, not the block
patch: {
// Use the block's _key to target it specifically
set: {
[`body[_key=="${block._key}"]._type`]: 'newCtaButton',
[`body[_key=="${block._key}"].newField`]: 'new value',
// Add other new fields as needed
}
}
}))Here's a more complete example using the Sanity client:
import {createClient} from '@sanity/client'
const client = createClient({
projectId: 'your-project-id',
dataset: 'your-dataset',
token: 'your-token',
apiVersion: '2025-05-16',
useCdn: false
})
async function migrateCtaButtons() {
// Fetch all documents with ctaButton blocks
const docs = await client.fetch(`
*[_type == "yourDocType" && body[]._type == "ctaButton"]
`)
for (const doc of docs) {
const blocksToUpdate = doc.body.filter(block => block._type === 'ctaButton')
// Build a single patch for all blocks in this document
const setOperations = {}
blocksToUpdate.forEach(block => {
const path = `body[_key=="${block._key}"]`
setOperations[`${path}._type`] = 'newCtaButton'
setOperations[`${path}.newField`] = 'default value'
// Add any other new fields you need
})
// Apply the patch
await client
.patch(doc._id)
.set(setOperations)
.commit()
console.log(`Updated ${blocksToUpdate.length} blocks in ${doc._id}`)
}
}
migrateCtaButtons()Path Syntax: Use body[_key=="abc-123"]._type to target specific blocks by their _key. As noted in the patches documentation, the array filter must use double quotes, which need to be escaped in JSON.
Single Patch: You can update multiple blocks in one document with a single patch operation by building up the set object with multiple paths.
Document ID: Always patch using the document's _id, never the block's _key. The _key is only used within the path expression.
Nested Fields: You can set deeply nested properties using dot notation: body[_key=="..."].nested.field
insert with replaceIf you need to completely replace blocks (not just change fields), you can use the insert operation with replace:
const blockIndex = doc.body.findIndex(block => block._key === targetKey)
await client
.patch(doc._id)
.insert('replace', `body[${blockIndex}]`, [{
_key: targetKey, // Keep the same key
_type: 'newCtaButton',
// ... all new fields
}])
.commit()The JSONMatch syntax documentation has more details on path expressions, and the content migration cheat sheet provides additional migration patterns for complex scenarios like this.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store