😎 Discover cool tips and tricks for customization in our next Developer Deep Dive virtual event - sign up now!

Rename a field across documents

By Knut Melvær

Migration script for renaming a field based on a GROQ query

renameField.ts

/* 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:

  • This script will exit if any of the mutations 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

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.

Contributor

Other schemas by author