Content migration cheat sheet

Common content migration patterns that can be run by the Sanity CLI

Below are content migration code snippets you can copy-paste and fit for your purpose. Requires familiarity with Sanity's schema and content migration tooling.

Rename a field in a document

import {defineMigration, at, setIfMissing, unset} from 'sanity/migrate'

export default defineMigration({
  title: 'Rename field from "oldFieldName" to "newFieldName"',
  migrate: {
    document(doc, context) {
      return [
        at('newFieldName', setIfMissing(doc.oldFieldName)),
        at('oldFieldName', unset())
      ]
    }
  }
})

Add a field with default value to all documents missing the field

Note: This example uses an async generator pattern (*migrate) to read out the document ID (_id) one by one and return the patch. This prevents the script from loading all documents into memory.

import {defineMigration, patch, at, setIfMissing} from 'sanity/migrate'

export default defineMigration({
  title: 'Add title field with default value',
  // documentTypes: ['post', 'article'], // only apply to certain document types
  async *migrate(documents, context) {
    for await (const document of documents()) {
      yield patch(document._id, [
        at('title', setIfMissing('Default title')),
      ])
    }
  }
})

Migrate a reference field into an array of references

import { defineMigration, at, setIfMissing, insert, unset} from 'sanity/migrate'

export default defineMigration({
  title: 'Convert a reference field into an array of references'
	documentTypes: ['product'],
	filter: 'defined(category) && !defined(categories)'
  migrate: {
    document(product) {
      return [
        at('categories', setIfMissing([])),
        at('categories', insert(product.category])),
        at('category', unset())
      ]
    }
  }
})

A string field into a Portable Text array

import {pathsAreEqual, stringToPath} from 'sanity'
import {defineMigration, set} from 'sanity/migrate'

const targetPath = stringToPath('some.path')

export default defineMigration({
  title: 'Convert a string into a Portable Text array',

  migrate: {
    string(node, path, ctx) {
      if (pathsAreEqual(path, targetPath)) {
        return set([
          {
            style: 'normal',
            _type: 'block',
            children: [
              {
                _type: 'span',
                marks: [],
                text: node,
              },
            ],
            markDefs: [],
          },
        ])
      }
    },
  },
})

Migrate inline objects into references

This example shows you how you can convert an inline object in an array field into a new document, and replace the array item with a reference to that new document.

You can also use this in Portable Text fields, and use .filter({_type}) => _type == "blockType") to only convert specific custom blocks.

// npm install lodash
import {deburr} from 'lodash'
import {at, createIfNotExists, defineMigration, replace, patch} from 'sanity/migrate'

/**
 * if you want to make sure you don't create many duplicated
 * documents from the same pet, you can generate an ID for it 
 * that will be shared for all pets with the same name
 **/
function getPetId(pet: {name: string}) {
  return `pet-${deburr(pet.name.toLowerCase())}`
}

export default defineMigration({
  title: 'Convert an inline object in an array into a document and reference to it',
  documentTypes: ['human'],
  filter: 'defined(pets) && count(pets[]._ref) > 0',
  migrate: {
    document(human) {
      const currentPets = human.pets
      // migrate any pet object to a new document
      if (Array.isArray(currentPets) && currentPets.length > 0) {
        return currentPets
          // skip pets that have already been converted to a reference
          .filter((pet) => !pet._ref)
          .flatMap((pet) => {
            const petId = getPetId(pet)

            // avoid carrying over the array _key to the pet document
            const {_key, ...petAttributes} = pet

            return [
              createIfNotExists({
                _id: petId,
                _type: 'pet',
                ...petAttributes,
              }),
							patch(human._id, at(['pets'], replace([{_type: 'reference', _ref: petId}], {_key}))),
            ]
          })
      }
    },
  },
})

Migrate a document type

Gotcha

The _id and _type attributes/fields on documents are immutable; that is, they can't be changed once they are set; there is no straightforward way to change these using the content migration tooling.

The simplest and most controlled way of approaching the migration of a document _type and _id, is:

  • export your dataset (sanity dataset export <dataset>, add --no-assets if you're not planning to do anything with these)
  • Untar the export file (tar -xzvf <dataset>.tar.gz)
  • Open the NDJSON of your dataset (<dataset>.ndjson)
  • Use whatever method to find and replace all that you find suitable
  • Re-import your dataset with the --replace flag (sanity dataset import <dataset>.ndsjon <dataset> --replace)

Always ensure you have a backup of your dataset and triple-check before changing content in production.

Was this article helpful?