@sanity/diff-patch

Generate Sanity patch mutations by comparing two documents or values. This library creates conflict-resistant patches designed for collaborative editing environments where multiple users may be editing the same document simultaneously.
Objectives
- Conflict-resistant patches: Generate operations that work well in 3-way merges and collaborative scenarios
- Performance: Optimized for real-time, per-keystroke patch generation
- Intent preservation: Capture the user's intended change rather than just the final state
- Reliability: Consistent, well-tested behavior across different data types and editing patterns
Used internally by the Sanity App SDK for its collaborative editing system.
Installation
Requires Node.js 22.12.0 or later. This package ships ES modules only.
To work on this repository, use Node.js 24 (nvm use) and the pnpm version specified in package.json.
npm install @sanity/diff-patch
API Reference
diffPatch(source, target, options?)
Generate patch mutations to transform a source document into a target document.
Parameters:
source: DocumentStub - The original document
target: DocumentStub - The desired document state
options?: PatchOptions - Configuration options
Returns: SanityPatchMutation[] - Array of patch mutations
Options:
interface PatchOptions {
id?: string
basePath?: Path
ifRevisionID?: string | true
}
Example:
import {diffPatch} from '@sanity/diff-patch'
const source = {
_id: 'movie-123',
_type: 'movie',
_rev: 'abc',
title: 'The Matrix',
year: 1999,
}
const target = {
_id: 'movie-123',
_type: 'movie',
title: 'The Matrix Reloaded',
year: 2003,
director: 'The Wachowskis',
}
const mutations = diffPatch(source, target, {ifRevisionID: true})
diffValue(source, target, basePath?)
Generate patch operations for values without document wrapper.
Parameters:
source: unknown - The original value
target: unknown - The desired value state
basePath?: Path - Base path to prefix operations (default: [])
Returns: SanityPatchOperations[] - Array of patch operations
Example:
import {diffValue} from '@sanity/diff-patch'
const source = {
name: 'John',
tags: ['developer'],
}
const target = {
name: 'John Doe',
tags: ['developer', 'typescript'],
active: true,
}
const operations = diffValue(source, target)
const operations = diffValue(source, target, ['user', 'profile'])
Collaborative Editing Example
The library generates patches that preserve user intent and minimize conflicts in collaborative scenarios:
const originalDoc = {
_id: 'blog-post-123',
_type: 'blogPost',
title: 'Getting Started with Sanity',
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system for modern applications.',
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers controll over the entire stack.',
},
{
_key: 'conclusion',
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
],
}
const userAChanges = {
...originalDoc,
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system for modern applications.',
},
{
_key: 'conclusion',
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers control over the entire stack.',
},
],
}
const userBChanges = {
...originalDoc,
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system that gives developers control over the entire stack.',
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers control over the entire stack.',
},
{
_key: 'conclusion',
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
],
}
const patchA = diffPatch(originalDoc, userAChanges)
const patchB = diffPatch(originalDoc, userBChanges)
const finalMergedResult = {
_id: 'blog-post-123',
_type: 'blogPost',
title: 'Getting Started with Sanity',
paragraphs: [
{
_key: 'intro',
_type: 'paragraph',
text: 'Sanity is a complete content operating system that gives developers control over the entire stack.',
},
{
_key: 'conclusion',
_type: 'paragraph',
text: 'Learning Sanity will help you take control of your content workflow.',
},
{
_key: 'benefits',
_type: 'paragraph',
text: 'It offers real-time collaboration and gives developers control over the entire stack.',
},
],
}
Technical Details
String Diffing with diff-match-patch
When comparing strings, the library attempts to use diff-match-patch to generate granular text patches instead of simple replacements. This preserves editing intent and enables better conflict resolution.
Automatic selection criteria:
- String size limit: Strings larger than 1MB use
set operations
- Change ratio threshold: If >40% of text changes (determined by simple string length difference), uses
set (indicates replacement vs. editing)
- Small text optimization: Strings <10KB will always use diff-match-patch
- System key protection: Properties starting with
_ (e.g. _type, _key) always use set operations as these are not typically edited by users
Performance rationale:
These thresholds are based on performance testing of the underlying @sanity/diff-match-patch library on an M2 MacBook Pro:
- Keystroke editing: 0ms for typical edits, sub-millisecond even on large strings
- Small insertions/pastes: 0-10ms for content <50KB
- Large insertions/deletions: 0-50ms for content >50KB
- Text replacements: Can be 70ms-2s+ due to algorithm complexity
The 40% change ratio threshold catches problematic replacement scenarios while allowing the algorithm to excel at insertions, deletions, and small edits.
Migration from v5:
Version 5 allowed configuring diff-match-patch behavior with lengthThresholdAbsolute and lengthThresholdRelative options. Version 6 removes these options in favor of tested defaults that provide consistent performance across real-world editing patterns. This allows us to change the behavior of this over time to better meet performance needs.
Array Handling
Keyed arrays: Arrays containing objects with _key properties are diffed by key rather than index, producing more stable patches for collaborative editing.
Index-based arrays: Arrays without keys are diffed by index position.
Undefined values: When undefined values are encountered in arrays, they are converted to null. This follows the same behavior as JSON.stringify() and ensures consistent serialization. To remove undefined values before diffing:
const cleanArray = array.filter((item) => typeof item !== 'undefined')
System Keys
The following keys are ignored at the root of the document when diffing a document as they are managed by Sanity:
_id
_type
_createdAt
_updatedAt
_rev
Error Handling
- Missing document ID: Throws error if
_id differs between documents and no explicit id option provided
- Immutable _type: Throws error if attempting to change
_type at document root
- Multi-dimensional arrays: Not supported, throws
DiffError
- Invalid revision: Throws error if
ifRevisionID: true but no _rev in source document
License
MIT © Sanity.io