Migration script for document types
This migration scripts lets you migrate documents to a new type.
By Knut Melvær
migrateDocumentType.ts
import {createClient} from '@sanity/client'
type Doc = {
_id: string
_rev?: string
_type: string
incomingReferences?: Doc[]
}
const token = process.env.SANITY_TOKEN
const projectId = process.env.SANITY_PROJECT_ID
const dataset = process.env.SANITY_DATASET
const apiVersion = '2023-03-01'
const client = createClient({
apiVersion,
projectId,
dataset,
token,
})
const OLD_TYPE = 'movie'
const NEW_TYPE = 'film'
const fetchDocuments = () =>
client.fetch(
`*[_type == $oldType][0...10] {..., "incomingReferences": *[references(^._id)]{...}}`,
{oldType: OLD_TYPE}
)
const buildMutations = (docs: Doc[]) => {
const mutations: any = []
docs.forEach((doc) => {
console.log('movie', doc._id)
// Updating an document _type field isn't allowed, we have to create a new and delete the old
const newDocId = `${doc._id}-migrated`
const newDocument = {...doc, ...{_id: newDocId, _type: NEW_TYPE}}
delete newDocument.incomingReferences
delete newDocument._rev
mutations.push({create: newDocument})
if (!doc.incomingReferences) {
return
}
// Patch each of the incoming references
doc.incomingReferences.forEach((referencingDocument) => {
console.log('ref', referencingDocument._id)
// ⚠️ We're assuming the field is named the same as the type!
// There might be another structure involved, perhaps an array, that needs patching
const updatedReference = {
[NEW_TYPE]: {
_ref: newDocId,
_type: 'reference',
},
}
mutations.push({
id: referencingDocument._id,
patch: {
set: updatedReference,
unset: [OLD_TYPE],
ifRevisionID: referencingDocument._rev,
},
})
})
// Apply the delete mutation after references have been changed
mutations.push({delete: doc._id})
})
return mutations.filter(Boolean)
}
const createTransaction = (mutations: any) => {
return mutations.reduce((tx: any, mutation: any) => {
if (mutation.patch) {
return tx.patch(mutation.id, mutation.patch)
}
if (mutation.delete) {
return tx.delete(mutation.delete)
}
if (mutation.create) {
return tx.createIfNotExists(mutation.create)
}
}, client.transaction())
}
const migrateNextBatch: any = async () => {
const documents = await fetchDocuments()
if (documents.length === 0) {
console.log('No more documents to migrate!')
return null
}
const mutations = buildMutations(documents)
const transaction = createTransaction(mutations)
await transaction.commit()
return migrateNextBatch()
}
migrateNextBatch().catch((err: any) => {
console.error(JSON.stringify(err, null, 2))
process.exit(1)
})
This example shows how you can perform a migration where a document _type field is changing. It will migrate documents in batches of 10 and continue patching until no more documents are returned from the query.
A few things to note:
- Changing the
_typefield on a document isn't allowed. The solution to this is to create a duplicate with a new_idand_type, then delete the old document and patch all referencing documents with the new_id - This script will exit if any of the patches on the referencing documents fail due to a revision mismatch (which means the document was edited between fetch => update)
- The query must eventually return an empty set, or else this script will continue indefinitely
- There's no guard against losing data on the old, as it might change between fetch and the creation of the new document
Run this script with the command npx sanity exec --with-user-token migrateDocumentType.ts in a studio folder. This script deletes and changes data, so it might be wise to export your dataset first.
Contributor

Knut Melvær
Knut is a principal developer marketing manager at Sanity.io
Norway