Migrating data from old to new field using Sanity's Patches API

5 replies
Last updated: Oct 6, 2021
Hello team, thank you for the great product.I have a question about migrating data from old to new field. In
this document , we have a new field called Body (rich text) and old field called Content (reference). Inside the linked Content document , we have Body field, which is rich text field.Question: How can we programmatically copy data from Body (inside Content field) to the new Body field? I know we can do it manually but unfortunately we have 1000+ posts so it's really hard to do by hand.
AI Update

Great question! This is a perfect use case for Sanity's migration tooling. You'll want to use the defineMigration API to programmatically copy the Body field from your referenced Content documents into the new Body field on your main documents.

Step 1: Create the Migration Script

First, generate a new migration:

npx sanity@latest migration create "Copy body from content reference"

Step 2: Write the Migration Logic

In the generated index.ts file, you'll need to:

  1. Fetch the referenced Content document using GROQ
  2. Extract the Body field from it
  3. Copy it to the new Body field on the main document

Here's what the migration script should look like:

import {defineMigration, at, set} from 'sanity/migrate'

export default defineMigration({
  title: 'Copy Body from Content reference to new Body field',
  documentTypes: ['yourDocumentType'], // Replace with your document type
  
  migrate: {
    async document(doc, context) {
      // Check if Content reference exists and new Body doesn't
      if (doc.Content?._ref && !doc.Body) {
        try {
          // Fetch the referenced Content document using GROQ
          const contentDoc = await context.client.fetch(
            `*[_id == $refId][0]{ Body }`,
            { refId: doc.Content._ref }
          )
          
          // If the Content document has a Body field, copy it
          if (contentDoc?.Body) {
            return [
              at('Body', set(contentDoc.Body))
            ]
          }
        } catch (error) {
          console.error(`Failed to fetch content for doc ${doc._id}:`, error)
        }
      }
      
      // Return empty array if no changes needed
      return []
    }
  }
})

The key difference from typical Sanity Client usage is that within migrations, you use context.client.fetch() with GROQ queries to retrieve data. This is the documented pattern as shown in this recipe for updating references.

Step 3: Test with Dry Run

Always test first without making changes:

sanity migration run "Copy body from content reference"

This will show you what changes would be made without actually modifying your data.

Step 4: Execute the Migration

Once you're satisfied with the dry run results, execute it on your dataset:

sanity migration run "Copy body from content reference" --dataset <your-dataset-name>

Important Considerations

  • Graceful error handling: The script includes try-catch blocks because migration scripts should handle failures gracefully, especially with large datasets where some references might be broken or missing.

  • Idempotent design: The script checks if doc.Body already exists before copying, making it safe to run multiple times without duplicating data.

  • Performance: The migration tool automatically batches mutations to avoid rate limits, which is helpful for your 1000+ posts.

  • Backup: Consider backing up your dataset before running the migration on production data.

If you need to handle more complex scenarios (like merging content or transforming the Body field during the copy), you can add additional logic within the document transformation function. The content migration documentation has more examples of advanced transformations.

Hey
user M
. Thanks for the response.On the
Patches API docs, Can you show me where I can take the content from Body in the referenced document, please?
Using the JS client it would look something like the script below. I haven't tested it, so there may be some typos I haven't caught! Note that you would need to set up some sort of queueing if you're going through hundreds of documents to avoid hitting the API rate limit .
//import and configure your sanity client
const sanityClient = require('@sanity/client')

const client = sanityClient({
  projectId: <your-project-id>',
  dataset: <your-dataset-name>,
  apiVersion: '2021-03-29', 
  token: <your-read-write-token>,
})


const mutateDocs = async () => {
  //fetch the docs that need to be mutated
  const docs = await client.fetch(`*[_type == <your-type-to-change>]`)
  for (const doc of docs) {
    const { _id, body, content } = doc
    //get the content from the referenced document
    const resolvedContent = await client.fetch(`*[_id == $contentRef] { body }`, { contentRef: content._ref })
    client.patch(_id)
          .set({
             body: resolvedContent
          })
         .commit()
         .then(updatedDoc => console.log(`Hurray, the document: ${id} was updated`))
         . catch(err => console.log('Update failed', err.message))
  }
}
Using the JS client it would look something like the script below. I haven't tested it, so there may be some typos I haven't caught! Note that you would need to set up some sort of queueing if you're going through hundreds of documents to avoid hitting the API rate limit .
//import and configure your sanity client
const sanityClient = require('@sanity/client')

const client = sanityClient({
  projectId: <your-project-id>',
  dataset: <your-dataset-name>,
  apiVersion: '2021-03-29', 
  token: <your-read-write-token>,
})


const mutateDocs = async () => {
  //fetch the docs that need to be mutated
  const docs = await client.fetch(`*[_type == <your-type-to-change>]`)
  for (const doc of docs) {
    const { _id, body, content } = doc
    //get the content from the referenced document
    const resolvedContent = await client.fetch(`*[_id == $contentRef] { body }`, { contentRef: content._ref })
    client.patch(_id)
          .set({
             body: resolvedContent
          })
         .commit()
         .then(updatedDoc => console.log(`Hurray, the document: ${id} was updated`))
         . catch(err => console.log('Update failed', err.message))
  }
}
Great. Thank you so much!!
Happy to help! Let us know if you're still hitting stumbling blocks!

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.

Was this answer helpful?