Knut Melvær
Knut is a principal developer marketing manager at Sanity.io
Migration script for renaming a field based on a GROQ query
/* eslint-disable no-console */
import {getCliClient} from 'sanity/cli'
import {Transaction} from '@sanity/client'
type Doc = {
_id: string
_rev: string
name: string
}
type DocPatch = {
id: string
patch: {
set: {fullname: string}
unset: string[]
ifRevisionID: string
}
}
// Gets the client configuration from `sanity.cli.ts` and returns a client.
// Will include write token when run with `sanity exec --with-user-token`
const client = getCliClient()
// Fetch the documents we want to migrate, and return only the fields we need.
const fetchDocuments = () =>
client.fetch(`*[_type == 'author' && defined(name)][0...100] {_id, _rev, name}`)
// Build a patch for each document, represented as a tuple of `[documentId, patch]`
const buildPatches = (docs: Doc[]) =>
docs.map(
(doc: Doc): DocPatch => ({
id: doc._id,
patch: {
set: {fullname: doc.name},
unset: ['name'],
// this will cause the migration to fail if any of the documents has been
// modified since it was fetched.
ifRevisionID: doc._rev,
},
})
)
const createTransaction = (patches: DocPatch[]): Transaction =>
patches.reduce(
(tx: Transaction, patch: DocPatch) => tx.patch(patch.id, patch.patch),
client.transaction()
)
const commitTransaction = (tx: Transaction) => tx.commit()
const migrateNextBatch = async (): Promise<void> => {
const documents = await fetchDocuments()
const patches = buildPatches(documents)
if (patches.length === 0) {
console.log('No more documents to migrate!')
process.exit(1)
}
console.log(
`Migrating batch:\n %s`,
patches.map((patch) => `${patch.id} => ${JSON.stringify(patch.patch)}`).join('\n')
)
const transaction = createTransaction(patches)
await commitTransaction(transaction)
return migrateNextBatch()
}
migrateNextBatch().catch((err: any) => {
console.error(err)
process.exit(1)
})
NOTE: We have shipped content migration tooling for Sanity (v3.27.0) which makes the approach described here outdated.
Go to the documentation and cheat sheet to learn how to do this with the proper tooling.
This example shows how you may write a migration script that renames a field (for example, name
to fullname
) on a specific document type (author
).
This will migrate documents in batches of 100 and continue patching until no more documents are returned from the query.
This script can safely be run, even if documents are being concurrently modified by others. If a document gets modified in the time between fetch and the commit patch, this script will fail, but can safely be re-run multiple times until it eventually runs out of documents to migrate.
A few things to note:
Run this script with the command npx sanity exec migrations/renameField.ts --with-user-token
in a studio folder. The script requires you to have a sanity.cli.ts
configuration file with your project ID and dataset defined.
It might be wise to export the dataset first since this changes data.
Knut is a principal developer marketing manager at Sanity.io
This can be used for blogs or articles where you want to possibly display next and previous article buttons
Go to Get current post, previous post, and next postHow to automatically generate linked headings with a custom serializer
Go to Anchored Headings for Portable TextMigration script to convert plain text to block content across your content lake
Go to Migrate plain text field to Portable TextSimple content type for a question and answer pattern
Go to Frequently asked questions