CoursesHandling schema changes confidentlyMaking the content migration (more) idempotent
Certification
Sanity developer certification
Track
Sanity developer essentials

Handling schema changes confidently

Lesson
8

Making the content migration (more) idempotent

Make it safe(r) to run the content migration multiple times using GROQ filters and idempotency keys.

Log in to mark your progress for each Lesson and Task

Idempotent or idempotency in the context of data and content migration means that you should be able to run a migration multiple times with the same result. In this case, the script is already fairly idempotent because format is never overwritten if it’s already set. However, learning more about how to prevent a content migration script from incurring unintentional changes can be useful.

You will learn two approaches:

  • Filtering only the documents that should be migrated
  • Adding an idempotence key to skip documents that have already been migrated once

In the content migration example in this course module, you filtered on the document type event and added transactions for all the documents. You can replace this with the filter property that accepts a simple GROQ filter (Joins are not supported). Often, you can express the conditional state of documents that need to be migrated.

Adapt the content migration script with a filter to only target documents with a value for eventType and no value for format
migrations/replace-event-type-with-event-format/index.ts
import {defineMigration, at, setIfMissing, unset} from 'sanity/migrate'
const from = 'eventType'
const to = 'format'
export default defineMigration({
title: 'Replace event type with event format',
// documentTypes: ['event'],
filter: '_type == "event" && defined(eventType) && !defined(format)',
migrate: {
document(doc, context) {
return [at(to, setIfMissing(doc[from])), at(from, unset())]
},
},
})

Note: If you run this content migration script, you shouldn’t get any output because all your documents have been migrated.

Some content migrations can’t be made idempotent with filters. In this case, you can add an idempotency key to mark it as migrated. If you store the keys in an array, you can even keep track of all the migrations a document has been subjected to.

Add an idempotence key to the content migration script
migrations/replace-event-type-with-event-format/index.ts
import {defineMigration, at, setIfMissing, unset, insert} from 'sanity/migrate'
// should be unique for the migration but never change
const idempotenceKey = 'xyz'
const from = 'eventType'
const to = 'format'
export default defineMigration({
title: 'Replace event type with event format',
// documentTypes: ['event'],
filter: '_type == "event" && defined(eventType) && !defined(format)',
migrate: {
document(doc, context) {
if ((doc?._migrations as string[] || []).includes(idempotenceKey)) {
// Document already migrated, so we can skip
return
}
return [
at(to, setIfMissing(doc[from])),
at(from, unset()),
//… add idempotence key
at('_migrations', setIfMissing([])),
at('_migrations', insert(idempotenceKey, 'after', 0)),
]
},
},
})

In this example, you also see the pattern for inserting into an array field.

Courses in the "Sanity developer essentials" track