
Backing Up and Restoring Your Sanity Data
To back up your Sanity data, export each dataset with npx sanity datasets export production production.tar.gz, which downloads every document and asset into a single archive, and schedule that command in CI so it runs automatically. On Enterprise plans you can also turn on Sanity's managed Backups feature with sanity backups enable production, which takes a daily snapshot and keeps it for a year. To restore, import the archive with npx sanity datasets import production.tar.gz production --replace. For a single document, use the Studio's history to restore an earlier revision, or the History API to recover one that was deleted.
Sanity runs the Content Lake on redundant infrastructure, so you don't need to worry about a disk failing. What backups protect you from is everything else: a migration script with a bug, an editor deleting the wrong thing, or a token that leaks and gets misused. This guide covers the layers of protection available, how exports work, automating them, Sanity's managed backups, and how to restore anything from a whole dataset to one document.
The Layers of Protection
Sanity gives you several overlapping tools. Each covers a different kind of mistake:
| Tool | Protects against | Scope | Availability |
|---|---|---|---|
| Document history | Bad edits to a single document | One document | All plans, with retention depending on plan |
sanity datasets export | Anything, if you have a recent export | Whole dataset | All plans |
| Managed Backups | Data loss and audits, without running anything yourself | Whole dataset | Certain Enterprise plans |
| Cloud Clone and aliases | Risky changes, by working on a copy | Whole dataset | Enterprise |
Document history retention depends on your plan: three days on Free, 90 days on Growth and 365 days on Enterprise, according to Sanity's documentation. That's helpful for undoing edits, but it isn't a backup. It doesn't help if a dataset is deleted, and on the Free plan three days goes quickly. Sanity document history and revisions explained goes deeper into how history works.
The practical baseline for every project, whatever the plan, is regular exports stored somewhere you control.
Exporting a Dataset
Run the export from your Studio folder, where the CLI already knows your project ID:
npx sanity datasets export production production.tar.gz
The first argument is the dataset and the second is the output file. Leave them out and the CLI prompts for both. To export from another project, add --project <project-id>.
The result is a gzipped tarball containing:
data.ndjson: every document, one JSON object per line, including drafts and the hidden system documents plugins use.assets.json: a map of asset document IDs to the URLs they were downloaded from.images/andfiles/: the binary files themselves.
Because the binaries are included, an export is a complete copy. You could import it into a brand new project and have everything back. The flags that change what's exported are:
| Flag | Effect |
|---|---|
--no-assets | Skips binaries and strips asset references. Smaller, but not a complete backup |
--raw | Exports documents as stored, without rewriting asset references |
--no-drafts | Exports only published documents |
--types post,author | Exports only the listed document types |
--mode cursor | Pages through the dataset, faster for large datasets but may miss edits made during the export |
--overwrite | Overwrites an existing file with the same name |
For backups, use the defaults. A backup without drafts or assets is a backup you'll regret at the worst possible moment.
Exports count towards your project's API usage, so a daily export of a very large dataset is worth keeping an eye on in the usage dashboard. Sanity pricing explains the quotas.
Automating Exports With GitHub Actions
A backup you have to remember to run isn't a backup. Schedule the export in CI and store the archive somewhere durable. Create an API token with read access in your project's API settings, and add it to your repository's secrets as SANITY_AUTH_TOKEN, which the CLI reads automatically:
# .github/workflows/sanity-backup.yml
name: Sanity backup
on:
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
jobs:
backup:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Export dataset
env:
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
run: |
npx --yes sanity@latest datasets export production \
"production-$(date +%F).tar.gz" --project ${{ vars.SANITY_PROJECT_ID }}
- name: Upload archive
uses: actions/upload-artifact@v4
with:
name: sanity-production-backup
path: production-*.tar.gz
retention-days: 30
Workflow artifacts are fine for a small project, but they expire. For anything important, upload the file to storage you control instead, such as an S3 or R2 bucket with versioning and a lifecycle rule that keeps, say, 30 daily and 12 monthly copies. Keep at least one copy outside the account your site is hosted in.
Exporting From Your Own Code
The CLI wraps the @sanity/export package, which you can call directly if you need to stream the archive somewhere or filter documents:
// scripts/backup.ts
import { createClient } from '@sanity/client';
import { exportDataset } from '@sanity/export';
const client = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: 'production',
apiVersion: '2025-02-19',
token: process.env.SANITY_READ_TOKEN,
useCdn: false,
});
const result = await exportDataset({
client,
dataset: 'production',
outputPath: `./production-${new Date().toISOString().slice(0, 10)}.tar.gz`,
});
console.log(`Exported ${result.documentCount} documents and ${result.assetCount} assets`);
Use the named exportDataset import. The package's default export is deprecated. The client needs a token, even for public datasets, so drafts are included.
Managed Backups on Enterprise
On Enterprise plans that include it, Sanity can take backups for you. Backups are off by default and enabled per dataset:
npx sanity backups enable production
Once enabled, Sanity backs up the dataset once a day, at a time you can't change, and the first backup can take up to 24 hours to appear. Daily backups are kept for 365 days, and weekly backups for a further two years. Backups are stored offsite with a third party for redundancy.
List and download backups with:
npx sanity backups list production --limit 10
npx sanity backups download production --backup-id <backup-id> --out production-backup.tar.gz
Two details catch people out:
- Permissions: a Developer can enable backups, but listing and downloading them requires the Administrator role, for users and tokens alike.
- Deleted datasets: if you delete a dataset and create a new one with the same name, backups must be enabled again, and the old dataset's backups are no longer reachable through the CLI. Contact support if you need them.
Managed backups don't include comments or document history. They also only run once a day, so if you need more frequent copies, schedule your own exports as well.
Restoring a Dataset
A downloaded backup and a CLI export have the same format, and both are restored with sanity datasets import.
Restore Into a New Dataset First
Unless production is completely gone, don't import straight over it. Restore into a separate dataset, check it, and then decide what to do:
npx sanity datasets create restore-check
npx sanity datasets import production-2026-11-03.tar.gz restore-check
Point a local Studio at restore-check and compare it with production. Often you'll find you only need a handful of documents back, which you can copy across without touching anything else. If a backup came from a different dataset than the one you're importing into, add --allow-assets-in-different-dataset.
Overwrite Production
When you do need to roll production back, import with --replace, which overwrites every document whose _id already exists:
npx sanity datasets import production-2026-11-03.tar.gz production --replace
Without --replace, the import fails for documents that already exist. Use --missing instead if you only want to bring back documents that were deleted, leaving everything else as it is.
An import is not a point-in-time reset. It writes the documents in the archive, but never removes documents that were created after the backup was taken. To get an exact copy of the backup's state, either delete and recreate the dataset before importing, or remove the extra documents with a script that compares the IDs in the dataset with those in data.ndjson.
Before You Restore
A restore writes thousands of documents in a short time, which triggers everything listening for changes:
- Ask editors to pause so their work isn't overwritten or mixed with restored content.
- Disable webhooks temporarily, or you'll trigger a burst of rebuilds. How to use Sanity webhooks shows where to find them.
- Take a fresh export of the current state, so the restore itself can be undone.
- Re-enable automations once you've checked the result.
Restoring a Single Document
Most restores are much smaller than a whole dataset.
A bad edit: open the document in Studio, open its history, select the revision you want and choose Restore. This creates a new draft with the old content, which you review and publish. It only works within your plan's history retention.
A deleted document: Studio won't show it any more, but the History API can find it. List recent transactions and look for delete mutations:
curl -H "Authorization: Bearer $SANITY_TOKEN" \
"https://$PROJECT_ID.api.sanity.io/v2021-06-07/data/history/production/transactions?excludeContent=true&reverse=true&limit=500"
Then fetch the document's last revision before it was deleted:
curl -H "Authorization: Bearer $SANITY_TOKEN" \
"https://$PROJECT_ID.api.sanity.io/v2021-06-07/data/history/production/documents/article-42?lastRevision=true"
The response wraps the document in a documents array. Write it back with client.createOrReplace(doc) using a token with write access.
A document older than your history: pull it out of an export. The documents are plain NDJSON, so jq can find one quickly:
tar -xzf production-2026-10-01.tar.gz
jq -c 'select(._id == "article-42")' */data.ndjson > article-42.ndjson
npx sanity datasets import article-42.ndjson production --replace
What Backups Don't Include
A dataset export or backup holds content. It doesn't hold your project's configuration, so make sure these are covered elsewhere:
- Studio code and schemas: keep them in Git. Without the schema, restored documents can't be edited sensibly.
- Project settings: CORS origins, webhooks, API tokens and members aren't in an export. Document them, or manage them with code where possible.
- Comments and tasks: these live in a separate companion dataset and aren't part of managed backups.
- Document history: an import creates fresh history from the moment of the import.
Keep a short runbook listing where backups are stored, who can access them and the exact restore commands. When something goes wrong, nobody wants to work it out from scratch.
Test Your Restores
A backup you've never restored is a hope rather than a plan. Once a quarter, import the latest export into a scratch dataset, open it in Studio and spot-check some documents and images, then delete the dataset. It takes ten minutes and tells you the archives are complete, the token still works and the process is documented.
If you regularly copy production into other datasets for development, the same commands apply, and how to migrate content between Sanity datasets covers that workflow in detail.
FAQ: Sanity Backups and Restores
Sanity stores data redundantly, and document history lets you undo recent edits. Dataset-level backups that you can download and restore are an Enterprise feature that must be enabled per dataset. On other plans, schedule your own exports with sanity datasets export.
Yes, by default. The archive contains every asset binary in images and files folders alongside data.ndjson. Only --no-assets leaves them out, which makes the export unsuitable as a complete backup.
Daily is a sensible default for most sites, with an extra manual export before risky operations like migrations or bulk deletes. Very active editorial teams may want more frequent exports, while rarely updated sites can do with weekly ones.
No. An import with --replace overwrites documents that exist in the backup, but leaves documents created after the backup untouched. To return to the exact state of the backup, delete and recreate the dataset first, or remove the extra documents with a script.
Often, yes. The History API's transactions endpoint lists recent deletions, and the documents endpoint with lastRevision=true returns the document's last state. This only works within your plan's history retention period.
A token with read access is enough to export a dataset, including drafts. Store it as a secret in your CI provider, set it as SANITY_AUTH_TOKEN for the CLI, and never give a backup job write access it doesn't need.
Conclusion
Backing up Sanity is simple once it's automated. sanity datasets export produces a complete archive of documents and assets, a scheduled CI job keeps fresh copies in storage you control, and Enterprise projects can add managed daily backups with long retention. Document history and the History API handle the smaller everyday mistakes.
Restoring is just as straightforward with sanity datasets import, as long as you remember that --replace overwrites rather than resets, and you restore into a scratch dataset first whenever you can. Keep your schemas in Git, write down the restore steps, and test them occasionally, so a bad day stays a minor inconvenience.


