it looks like our problems are caused by editors publishing drafts after backend token has updated docs, and thus overwriting it
You've correctly identified the classic race condition problem with drafts overwriting backend updates. The good news is Sanity provides a robust solution through optimistic concurrency control using the ifRevisionID parameter.
The Core Solution: Use ifRevisionID for Backend Mutations
When your backend updates documents, you should use the ifRevisionID parameter in your patch mutations. This prevents the exact scenario you're describing:
// In your backend code
await client
.patch(documentId)
.ifRevisionID(currentRevisionId) // Only apply if this revision matches
.set({ yourField: newValue })
.commit()If the revision ID doesn't match (because a draft exists or someone else modified it), the mutation will fail with a 409 Conflict status, allowing you to:
- Fetch the latest version
- Decide whether to merge changes or retry
- Avoid silently losing data
Why This Happens
The issue stems from Sanity's draft system:
- When editors start editing, a draft document (prefixed with
drafts.) is created - Your backend likely updates the published document (without the
drafts.prefix) - When editors publish, the draft replaces the published document entirely
- Any backend changes made between steps 1-3 are lost
Additional Strategies
For your backend:
- Always include
ifRevisionIDin mutations to detect conflicts - Consider checking if a draft exists before updating: query for
drafts.{documentId}and handle accordingly - You might update both the draft and published versions if a draft exists
For your editors (workflow improvements):
- Use custom document actions to warn editors if
_updatedAton the published doc is newer than when the draft was created - Implement a "Discard outdated drafts" action that checks timestamps
- Add visual indicators in Studio showing when published content is newer than the draft
Alternative architecture: If your backend updates are frequent and critical, consider whether editors should be modifying those fields at all. You might:
- Make certain fields read-only in Studio
- Use field-level permissions
- Structure your schema so backend-managed data lives in separate documents
Example: Detecting Conflicts
// Backend mutation with conflict handling
try {
const doc = await client.getDocument(docId)
await client
.patch(docId)
.ifRevisionID(doc._rev)
.set({ backendField: value })
.commit()
} catch (error) {
if (error.statusCode === 409) {
// Conflict detected - draft exists or doc changed
console.log('Conflict: document was modified')
// Handle: retry, merge, or alert
}
}The ifRevisionID approach is exactly what the Transactions documentation recommends for preventing lost updates in read-write cycles. It's Sanity's built-in solution for optimistic locking and will catch both draft conflicts and concurrent modifications.
Show original thread20 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.