
Sanity Document History and Revisions Explained
Sanity document history is the record of every transaction that changed a document, kept automatically by the Content Lake. In the Studio you open it from the document's status indicator or the History menu item, see who changed what and when, compare any earlier revision with the current one, and restore it with one click. Each change produces a new revision ID in the document's _rev field, and the History API lets you fetch a document as it was at any revision or point in time. How far back you can go depends on your plan: 3 days on Free, 90 days on Growth and 365 days on Enterprise.
History is one of the features editors appreciate most once they need it, and one of the most misunderstood. It's excellent for undoing a bad edit, but it isn't a backup. This guide covers how revisions work, the Studio history and review changes UI, restoring versions and deleted documents, the _rev field and optimistic locking, the History API endpoints, retention by plan, and how history compares with backups and datasets.
How Sanity Records Changes
The Studio doesn't save whole documents. Every edit is sent to the Content Lake as a small patch inside a transaction: set this field, insert this array item, unset that value. The Content Lake applies the transaction, records it in the document's history, and gives the document a new revision ID.
That design has a few useful consequences:
- Every change is attributed: each transaction records the user or token that made it and a timestamp.
- Any earlier state can be rebuilt: replaying transactions up to a point gives you the document as it was then.
- Drafts and published versions both have history: the draft (
drafts.post-1) and the published document (post-1) are separate documents, so editing and publishing each create entries. - Scripts are recorded too: changes made through the API, migrations and imports show up in history alongside Studio edits.
This is the same patch-based model that makes real-time collaboration in Sanity work. History is the stored trail of those patches.
Using History in the Studio
Open the History Panel
Open any document and either click the status indicator at the bottom of the editor ("Published 2 hours ago", for example) or open the ellipsis menu in the top-right corner and choose History. A panel opens beside the form with a list of events, newest first.
Each entry shows what happened and who did it. The labels you'll see include:
| Label | Meaning |
|---|---|
| Published | The draft was published to the API |
| Unpublished | The published version was removed, leaving the draft |
| Edited | Changes were made, usually grouped by editing session |
| Truncated | Revisions older than your plan's retention, merged into one item |
Review Changes
The changes view compares two points in the document's life. By default it shows what changed in the current draft since the last published version, field by field, with the author's avatar next to each change. Text changes are highlighted inline, and changed array items, images and references are shown as before and after values.
Click an earlier entry in the timeline to change the comparison. The form switches to a read-only view of the document as it was at that revision, and the changes panel shows everything that has happened since. To narrow the range, select a start and end point in the list.
This is the quickest way to answer questions like "who changed the price on this product?" or "what did this page say last Tuesday?".
Revert a Single Change
You don't have to roll back a whole document to undo one mistake. In the changes view, each changed field has a Revert option that puts just that field back to its earlier value in the current draft. This is ideal when an editor accidentally clears an image or overwrites a paragraph while the rest of their edits are correct.
Restore an Earlier Version
To roll back the whole document, select the revision you want in the history list and click Restore at the bottom of the read-only view. The Studio writes that version into the draft. Nothing reaches your website yet: review the draft and publish it when you're happy.
Restoring doesn't delete history. The restore is a new transaction, so you can undo it the same way if you pick the wrong revision.
Recover a Deleted Document
Deleting a document removes it from queries, but its history remains for the retention period. If you open the deleted document by its ID, for example from a link in a team chat or by editing the Studio URL, the Studio shows that it has been deleted and offers to restore the most recent revision. You can also fetch the last version through the History API, as shown below, and recreate it with the client.
If you don't know the ID, the transactions endpoint can help you find it, which is one reason to keep links to important documents in tickets and messages.
History and Content Releases
When a document version is published as part of a content release, select the Published pill at the top of the document, open the menu for the release edit and choose Inspect. You'll see the changes made between when the release version was created and when the release was published. Scheduled publishing in Sanity explains how releases and scheduling fit together.
Revisions and the _rev Field
Every document carries a _rev field containing the ID of the last transaction applied to it:
{
"_id": "post-1",
"_type": "post",
"_rev": "Kx9vNrSfQm2LZ1mBq4TwYc",
"_createdAt": "2026-09-14T08:21:05Z",
"_updatedAt": "2026-11-03T16:42:11Z",
"title": "Getting started with GROQ"
}
Treat _rev as an opaque string. It isn't a counter and it isn't ordered, so you can't compare two revisions to see which is newer. What you can do is check whether a document has changed since you last read it.
Optimistic Locking With ifRevisionId
Scripts that read a document, compute something and write it back can overwrite edits made in between. Pass the revision you read, and the Content Lake rejects the patch if the document has changed since:
import { createClient } from '@sanity/client'
const client = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: 'production',
apiVersion: '2025-02-19',
token: process.env.SANITY_WRITE_TOKEN,
useCdn: false,
})
const doc = await client.getDocument('post-1')
if (!doc) throw new Error('Not found')
try {
await client
.patch(doc._id)
.ifRevisionId(doc._rev)
.set({ wordCount: countWords(doc.body) })
.commit()
} catch (error) {
// 409 conflict: someone edited the document. Re-read and try again.
}
The Sanity Mutation API guide covers transactions, patches and conflict handling in more depth.
The History API
The Studio's history panel is built on public endpoints you can call yourself with a token that can read the dataset. They live under /data/history/<dataset>/ on your project's API host. The Sanity HTTP API shows how requests are authenticated and versioned.
| Endpoint | Returns |
|---|---|
GET /data/history/:dataset/documents/:id | The document at a revision or time |
GET /data/history/:dataset/transactions/:ids | Transactions for one or more documents, as NDJSON |
GET /data/history/:dataset/transactions | Transactions for the whole dataset, as NDJSON |
Fetch a Document at a Point in Time
The document endpoint accepts one of three mutually exclusive parameters:
revision: a_revvalue, to get the document exactly as it was after that transaction.time: an ISO timestamp, to get the document as it was at that moment.lastRevision=true: the latest revision, or for a deleted document, its last state before deletion.
# As it was at 9am on 1 October
curl -H "Authorization: Bearer $SANITY_TOKEN" \
"https://abc123xy.api.sanity.io/v2025-02-19/data/history/production/documents/post-1?time=2026-10-01T09:00:00Z"
# The last version of a deleted document
curl -H "Authorization: Bearer $SANITY_TOKEN" \
"https://abc123xy.api.sanity.io/v2025-02-19/data/history/production/documents/post-1?lastRevision=true"
The response wraps the result in a documents array.
List Transactions
The transactions endpoints return metadata about each change, one JSON object per line, including the transaction ID, timestamp, author and affected document IDs. You must pass excludeContent=true; other values are rejected. Useful parameters:
| Parameter | Purpose |
|---|---|
fromTime, toTime | Limit to a time range |
fromTransaction, toTransaction | Limit to a range of transaction or revision IDs |
authors | Comma-separated user IDs, to see one person's changes |
reverse | true to return the newest first |
limit | Cap the number of results, which Sanity recommends |
curl -H "Authorization: Bearer $SANITY_TOKEN" \
"https://abc123xy.api.sanity.io/v2025-02-19/data/history/production/transactions/post-1,drafts.post-1?excludeContent=true&reverse=true&limit=20"
Include both the published and draft IDs when you want the full story of a document.
Restore a Revision From Code
To restore programmatically, fetch the old version and write it back as a draft so an editor can review it before publishing:
// scripts/restore-revision.ts
type HistoryResponse = { documents: Array<Record<string, unknown> & { _id: string; _type: string }> }
async function restoreToDraft(id: string, time: string) {
const { documents } = await client.request<HistoryResponse>({
uri: `/data/history/production/documents/${id}`,
query: { time },
})
const previous = documents[0]
if (!previous) throw new Error(`No revision of ${id} at ${time}`)
const { _rev, _updatedAt, _createdAt, ...content } = previous
await client.createOrReplace({ ...content, _id: `drafts.${id}`, _type: previous._type })
console.log(`Restored ${id} as it was at ${time}. Review and publish in the Studio.`)
}
await restoreToDraft('post-1', '2026-10-01T09:00:00Z')
Writing to the draft rather than the published ID is deliberate. It mirrors the Studio's restore button and keeps a human in the loop.
Retention by Plan
History isn't kept forever. According to Sanity's documentation:
| Plan | History retention |
|---|---|
| Free | 3 days |
| Growth | 90 days |
| Enterprise | 365 days, or custom on request |
Retention counts back from today. Once a day, revisions older than the cutoff are merged into a single Truncated item and the older transactions are permanently deleted. The current draft and published versions are always kept, so truncation never loses your latest content, only the steps that led to it.
Two details are easy to miss:
- Upgrading doesn't bring history back: moving from Free to Growth extends the window from then on, but anything already truncated is gone.
- Downgrading truncates immediately: every document's history is cut to the new, shorter window.
Retention limits also help with privacy obligations such as GDPR, since old personal data in earlier revisions doesn't linger indefinitely. Current plan details are in Sanity pricing explained.
History vs Backups vs Datasets
History, backups and separate datasets solve different problems. Using the wrong one is a common gap in recovery plans.
| Document history | Dataset export (backup) | Separate dataset | |
|---|---|---|---|
| Scope | One document at a time | Every document in a dataset | A full independent copy |
| Time range | Plan retention (3 to 365 days) | As long as you keep the files | Snapshot at copy time |
| Survives dataset deletion | No | Yes | Yes |
| Includes asset files | No | Yes, by default | Yes |
| Best for | Undoing edits, auditing who changed what | Disaster recovery, compliance | Testing schema changes and migrations |
| Effort | Built in | Schedule it yourself | Create and copy when needed |
Restoring hundreds of documents one by one from history after a bad migration is slow and error-prone, and on the Free plan, three days of history can expire before anyone notices the damage. Use history for everyday mistakes, regular exports for disasters, and a separate dataset to test risky changes before they happen. The details are in Sanity backup and restore and managing datasets and environments.
FAQ: Sanity Document History
Open the document in the Studio and choose History from the ellipsis menu, or click the status indicator at the bottom of the editor. Each history entry shows the author and time, and the changes view marks every changed field with the editor's avatar.
No. Restoring writes the old version into the draft, so you can review it before publishing. The restore is recorded as a new transaction, which means you can undo it from history if you chose the wrong revision.
Yes, within your plan's history retention. Open the document by its ID and restore the most recent revision, or fetch it from the History API with lastRevision set to true and recreate it with the client. After the retention window, you need an export to recover it.
Three days on the Free plan, 90 days on Growth and 365 days on Enterprise, with custom retention available on request. Older revisions are merged into a single truncated entry each day, but the current draft and published versions are always kept.
It's the ID of the last transaction applied to the document, and it changes on every edit. You can use it to fetch that exact revision from the History API, or pass it to ifRevisionId when patching so the write fails if someone else changed the document first.
No. History covers individual documents for a limited time and disappears with the dataset if it's deleted. Schedule regular dataset exports for disaster recovery, and use history for undoing everyday edits and auditing changes.
Conclusion
Every change in Sanity is a transaction, and document history is the stored record of those transactions. In the Studio, the history panel and changes view show who changed what and when, let you revert a single field, and restore a whole document to any earlier revision as a draft you can review. Behind the UI, _rev identifies each revision, ifRevisionId protects scripts from overwriting newer edits, and the History API gives you documents at any time or revision plus a filterable log of transactions.
Know your plan's retention window, because truncated history can't be brought back. And keep history in its place: it's the right tool for everyday mistakes and audits, while scheduled exports and separate datasets cover the disasters. For the rest of a production setup, see Sanity best practices.


