Structured Content 2022: Join our conference to explore fresh perspectives on content and digital experiences →

Migration script for document types

By Knut Melvær

This migration scripts lets you migrate documents to a new type.

migrateDocumentType.js

import client from "part:@sanity/base/client";

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 => {
  const mutations = [];

  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 });

    // 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 => {
  return mutations.reduce((tx, mutation) => {
    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 = 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 => {
  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 _type field on a document isn't allowed. The solution to this is to create a duplicate with a new _id and _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 sanity exec --with-user-token migrateDocumentType.js in a studio folder. This script deletes and changes data, so it might be wise to export your dataset first.

Contributor

Other schemas by author