# How do you import content into Sanity safely: dry runs, draft prefixes, paused webhooks and rollback?

Reviewed by Martin Dejnicki · facts verified 25 October 2026 · canonical: https://deploi.ca/sanity/migration/cms-to-sanity/safe-import

## Short answer

A safe Sanity import adds its own controls, because `sanity datasets import` has no dry run, skips validation and can fire webhooks ([Sanity Docs](https://www.sanity.io/docs/cli-reference/cli-datasets); [Import data](https://www.sanity.io/docs/content-lake/importing-data), October 2026). Validate the import file, rehearse in a scratch dataset, pause webhooks and Functions, import as `drafts.` for live sites, choose `--replace` or `--missing`, and keep an export and ID list for rollback.

- **Dry run on sanity datasets import:** None: the command lists no dry-run flag. sanity migrations run dry-runs by default and can also write new documents fetched from an external source; validate an import file with sanity documents validate --file ([Sanity Docs, Datasets CLI command reference](https://www.sanity.io/docs/cli-reference/cli-datasets); [Sanity Learn, Scripting content migrations](https://www.sanity.io/learn/course/refactoring-content/scripting-content-migrations); [Sanity Docs, Migrations CLI command reference](https://www.sanity.io/docs/cli-reference/cli-migrations))
- **Document ID already in the target:** Import fails unless --replace or --missing. --replace overwrites the document, --missing skips it, and no mode deletes documents that aren't in the file ([Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data); [Sanity Docs, Development, staging, and production environments](https://www.sanity.io/docs/content-lake/environments))
- **Scripted write rate:** 25 mutations per second per IP address. Requests over it return 429; @sanity/client retries queries but not mutations; each mutation request body is capped at 4 MB, as of October 2026 ([Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data); [Sanity Docs, Technical limits](https://www.sanity.io/docs/content-lake/technical-limits))
- **Managed backups:** Certain Enterprise plans only, off by default. On Free and Growth the backup is your own export; document history covers 90 days on Growth, as of October 2026 ([Sanity Docs, Backups](https://www.sanity.io/docs/content-lake/backups); [Sanity, Plans that scale with you](https://www.sanity.io/pricing); [Sanity Docs, History experience](https://www.sanity.io/docs/user-guides/history-experience))

## Deploi's call

Treat an import into Sanity as one-way unless you built the undo before it ran: a validated file, a rehearsal in a scratch dataset, an export of the target and the list of IDs the import creates. Use --missing on any dataset editors already work in, and keep --replace for documents only your migration writes.

**MIGRATE: Import straight into Sanity's production dataset when**

- No live site reads the production dataset yet, so a bad import breaks nothing public and documents can land published, without the `drafts.` prefix.
- Every document ID comes from its source ID, so a rerun with --replace overwrites instead of duplicating.
- The NDJSON (newline-delimited JSON) file passes sanity documents validate --file, since the import has no dry run, and you hold the list of IDs it creates for rollback.

**PHASE: Import into a Sanity staging dataset first, then promote to production, when**

- Production already serves pages or holds editors' work, because an import can overwrite documents but never deletes them ([Sanity Docs](https://www.sanity.io/docs/content-lake/environments), October 2026).
- Your plan has a dataset and quota room to spare, since Free and Growth include 2 datasets, every copy counts toward that limit, and documents count across all datasets, as of October 2026.
- Webhooks or Functions react to production writes and can't stay paused for the whole run, so the first import has to land somewhere they don't fire.

## Does Sanity's import command have a dry run, and what replaces it?

No. `sanity datasets import`, Sanity's command-line interface (CLI) import, lists no dry-run flag. Sanity's refactoring course confirms it for the NDJSON (newline-delimited JSON) route ([Sanity Docs, Datasets CLI reference](https://www.sanity.io/docs/cli-reference/cli-datasets); [Sanity Learn](https://www.sanity.io/learn/course/refactoring-content/scripting-content-migrations), October 2026). The import skips validation too ([Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data); [Schema validation and the Content Lake](https://www.sanity.io/docs/content-lake/schema-validation-and-the-content-lake), October 2026). `sanity migrations run` dry-runs by default and can also write new documents fetched from an external source ([Sanity Learn](https://www.sanity.io/learn/course/refactoring-content/scripting-content-migrations); [Sanity Docs, Migrations CLI reference](https://www.sanity.io/docs/cli-reference/cli-migrations)), so a migration script dry-runs. For an NDJSON file, rehearse the import yourself; Deploi's September 2026 release scripts changed nothing without --apply. Safe importing is step 5 of [planning a migration to Sanity](https://deploi.ca/sanity/migration/cms-to-sanity).

**A safe import into Sanity, with the check for each step**

1. **Validate the file.** Run `sanity documents validate --file` on the NDJSON file or tarball ([Sanity Docs](https://www.sanity.io/docs/content-lake/schema-and-content-migrations)). Check: no errors.
1. **Rehearse.** Import into a scratch dataset you can delete. Check: counts per type match the source.
1. **Export the target.** Run `sanity datasets export`. Check: the file opens.
1. **Pause side effects.** Disable webhooks; keep Functions off the target. Check: no deliveries.
1. **Import.** Choose `--missing` or `--replace`; save the IDs the file creates. Check: a clean exit.
1. **Verify.** Run `sanity documents validate`. Check: results match the rehearsal.
1. **Switch side effects back on.** Re-enable webhooks and Functions. Check: one test edit fires once.

## What do --replace and --missing do, and which should a migration use?

`sanity datasets import` fails on a document ID that already exists unless you pass `--replace` (overwrite it) or `--missing` (skip it); a rerun doesn't update in place by default ([Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data), October 2026). No mode deletes: "Import never deletes documents" ([Sanity Docs, Development, staging, and production environments](https://www.sanity.io/docs/content-lake/environments), October 2026).

**Pick the import mode by who else writes to the target dataset**

| Mode | Existing ID in the target | Fits |
| --- | --- | --- |
| No flag | The import fails | A first load into an empty dataset |
| --replace | Overwritten by the file | Reruns of documents only your migration writes |
| --missing | Skipped, the target's version stays | Seeding a dataset editors already use |

The fit column is Deploi's judgment; Sanity's environments page advises --missing for moving development content into production. Sources: [Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data); [Sanity Docs, Datasets CLI command reference](https://www.sanity.io/docs/cli-reference/cli-datasets); [Sanity Docs, Development, staging, and production environments](https://www.sanity.io/docs/content-lake/environments). As of 24 October 2026.

Deploi's October 2026 import package for its Shopify data section uses `--replace`, made safe by ID prefixes on every document it writes. When Deploi moved its FAQ into a shared Sanity project in September 2026, it imported without `--replace`, so editors' versions were never overwritten.

> **--replace overwrites editors' work**
>
> A rerun with --replace overwrites every Sanity Studio edit made since the last run on the documents it touches. Before each rerun, list documents edited since then and leave them out.

## Should migrated content land in Sanity as drafts or as published documents?

Land migrated content as drafts when a live site already reads the target dataset, and as published documents when nothing public reads it yet. A draft is the document's ID with a `drafts.` prefix, and "Neither drafts nor versions are served to unauthenticated requests", so a front end that reads published content never shows them ([Sanity Docs, Drafts and versions](https://www.sanity.io/docs/content-lake/drafts-and-versions), October 2026). Two rules follow:

- **Point references at published IDs.** Never prefix a reference. "A published document can't hold a strong reference to unpublished content" (Sanity Docs, Drafts and versions), so a reference to a target that is still a draft has to be weak. Deploi's import notes for its Build vs. Buy hub, now 701 decision pages, bring unpublished content in under the `drafts.` prefix and never prefix references, as [publishing 701 pages at agent scale](https://deploi.ca/sanity/builds/agent-scale-publishing) describes.
- **Count drafts in the quota.** Sanity counts stored documents "(including drafts)" ([Sanity Docs, Plans and payments](https://www.sanity.io/docs/platform-management/plans-and-payments), October 2026), so a draft beside its published version is a second document (what counts as a Sanity document).

Decide before the first import, because changing later means a second import and a cleanup of the first (Deploi's judgment). Drafts still have to be published, which for a large import takes a script rather than the Studio, so plan that step before the first import (Deploi's judgment).

## What do you switch off in Sanity before an import?

Switch off the webhooks and Functions that react to the target dataset, because "Importing data can trigger your webhooks and functions and send high volumes of traffic to the endpoints they call" ([Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data), October 2026).

- **Webhooks.** Disable each one in Manage, Sanity's project settings. "When a webhook is disabled, all pending requests will be canceled" ([Sanity Docs, webhooks](https://www.sanity.io/docs/content-lake/webhooks), October 2026), so trigger one rebuild yourself after you switch them back on.
- **Functions.** "A deployed function has no on/off switch" ([Sanity Docs, Functions cheat sheet](https://www.sanity.io/docs/functions/functions-cheatsheet), October 2026). Sanity lists three ways around it, in its order of preference: scope the function to another dataset, remove it from the blueprint and redeploy, or return early when an environment variable is set. The last still counts every run as an invocation toward Functions usage.

Deploi's release runbook covers the webhook half: it has the revalidation webhook created only after the September 2026 bulk change, so the 256 changed FAQ answers wouldn't trigger 256 rebuilds. Functions or webhooks compares the two triggers.

## What limits and rules apply when a script writes content into Sanity?

A script that writes content into Sanity through the API (application programming interface) has to stay at 25 mutations per second or fewer per IP (Internet Protocol) address, keep each mutation request body under 4 MB, and add the array keys Sanity Studio adds for editors ([Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data); [Technical limits](https://www.sanity.io/docs/content-lake/technical-limits); [Learn](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/two-pass-import), October 2026). Requests over the rate return 429 Too Many Requests, and `@sanity/client` retries rate-limited queries, not mutations ([Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data)). Four rules follow:

- **Queue and batch.** Send several mutations per transaction and queue them under the rate, so a long run doesn't stall.
- **Key every array item.** "Studio auto-generates keys when editors create content through the UI; the API doesn't" ([Sanity Learn, Import content with the two-pass pattern](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/two-pass-import), October 2026).
- **Two passes for references.** Write `_weak: true` placeholders first and resolve them second (Sanity Learn, two-pass pattern). The CLI (command-line interface) import does this itself: references go in weak and turn strong once every document is in (Sanity Docs, Import data). Deploi's builds graph keeps all 2,940 of its references weak; reference-safe imports go deeper.
- **Rerun asset uploads freely.** The same image binary always gets the same ID, with no duplicate ([Sanity Learn, Uploading assets efficiently](https://www.sanity.io/learn/course/refactoring-content/uploading-assets-efficiently), October 2026). In the CLI import, an unavailable asset fails the run unless you pass `--allow-failing-assets` (Sanity Docs, Import data).

## How do you roll back an import into Sanity?

You roll back an import into Sanity by deleting the documents it created, from the saved list of their IDs, with the command-line interface (CLI) command `sanity documents delete`, which takes one or more IDs ([Sanity Docs, Documents CLI reference](https://www.sanity.io/docs/cli-reference/documents), October 2026). That removes new documents only. Documents a `--replace` run overwrote come back only from an export or from document history, so prepare both before the run:

- **Export the target first.** `sanity datasets export` works on every plan, exports count against the project's API (application programming interface) quota, and assets returning 401, 403 or 404 are left out ([Sanity Docs, Export a dataset](https://www.sanity.io/docs/content-lake/exporting-data); [Environments](https://www.sanity.io/docs/content-lake/environments); [Datasets CLI reference](https://www.sanity.io/docs/cli-reference/cli-datasets), October 2026). Sanity's checklist for production projects starts with this export ([Sanity Docs, Important considerations](https://www.sanity.io/docs/content-lake/important-considerations-for-schema-and-content-migrations)).
- **Expect no reset.** A restore with `--replace` "never removes documents that exist in the target dataset but not in the backup" ([Sanity Docs, Backups](https://www.sanity.io/docs/content-lake/backups), October 2026).
- **Don't count on managed backups.** They're on certain Enterprise plans and off by default (Sanity Docs, Backups; [Sanity pricing](https://www.sanity.io/pricing), October 2026). Document history covers 90 days on Growth ([Sanity Docs, History experience](https://www.sanity.io/docs/user-guides/history-experience)).

Deploi's October 2026 data-section wave shipped 406 documents and 159 images with an ID list for count checks and rollback. [Cutover and rollback](https://deploi.ca/sanity/migration/cms-to-sanity/cutover-and-rollback) covers undoing the go-live itself.

## Deploi's point of view

**Where we disagree.** The usual advice says --replace makes a Sanity import safe to rerun. It makes the import repeatable, not safe. A rerun with --replace overwrites every edit made since the last run, never removes documents you dropped from the source, and a restore with it leaves newer documents in place. Safety comes from what surrounds the command: ID prefixes on everything the import writes, a check for documents edited since the last run, and an ID list to delete from.

**What we've seen.** These controls come from deploi.ca's own Sanity releases. In September 2026 Deploi's release scripts changed nothing without --apply and skipped any document edited since the snapshot, and its release runbook has the revalidation webhook created only after the bulk change, so the 256 changed answers wouldn't trigger 256 rebuilds. The October 2026 wave of its Shopify data section went in as one package of 406 documents and 159 images, with check queries and an ID list for rollback.

**What this adds.** The root plans the migration and says to load staging first. This page is the import step itself: each failure mode Sanity documents for its import (no dry run, no validation, no deletes, webhook and Function triggers, rate limits, restores that aren't resets) paired with the control that covers it.

**What we refuse to promise.** We won't call an import on Free or Growth reversible unless an export of the target was taken before it ran. Managed backups are on certain Enterprise plans and off by default, and Growth's document history reaches back 90 days, as of October 2026, so the export is the only full copy you control.

## When this answer changes

- Sanity adds a dry-run or validation option to sanity datasets import
- Sanity changes the 25 mutations per second rate limit or the 4 MB mutation body limit
- Sanity gives deployed Functions an on/off switch
- Managed backups reach the Growth plan, or Growth's 90-day document history changes
- Sanity renames the datasets or documents CLI commands again

## Next: cut over to Sanity with a rollback plan

See how to run go-live day, share one site between two CMSs and undo the switch by layer. [Plan the cutover](https://deploi.ca/sanity/migration/cms-to-sanity/cutover-and-rollback)

## Sources

1. [Sanity Docs, Import data](https://www.sanity.io/docs/content-lake/importing-data), checked 24 October 2026
2. [Sanity Docs, Datasets CLI command reference](https://www.sanity.io/docs/cli-reference/cli-datasets), checked 24 October 2026
3. [Sanity Learn, Scripting content migrations](https://www.sanity.io/learn/course/refactoring-content/scripting-content-migrations), checked 24 October 2026
4. [Sanity Docs, Migrations CLI command reference](https://www.sanity.io/docs/cli-reference/cli-migrations), checked 24 October 2026
5. [Sanity Docs, Documents CLI command reference](https://www.sanity.io/docs/cli-reference/documents), checked 24 October 2026
6. [Sanity Docs, Development, staging, and production environments](https://www.sanity.io/docs/content-lake/environments), checked 24 October 2026
7. [Sanity Docs, Drafts and versions](https://www.sanity.io/docs/content-lake/drafts-and-versions), checked 24 October 2026
8. [Sanity Docs, Functions cheat sheet](https://www.sanity.io/docs/functions/functions-cheatsheet), checked 24 October 2026
9. [Sanity Docs, GROQ-powered webhooks](https://www.sanity.io/docs/content-lake/webhooks), checked 24 October 2026
10. [Sanity Docs, Technical limits](https://www.sanity.io/docs/content-lake/technical-limits), checked 24 October 2026
11. [Sanity Learn, Import content with the two-pass pattern](https://www.sanity.io/learn/course/migrating-content-from-webflow-to-sanity/two-pass-import), checked 24 October 2026
12. [Sanity Learn, Uploading assets efficiently](https://www.sanity.io/learn/course/refactoring-content/uploading-assets-efficiently), checked 24 October 2026
13. [Sanity Docs, Export a dataset](https://www.sanity.io/docs/content-lake/exporting-data), checked 24 October 2026
14. [Sanity Docs, Backups](https://www.sanity.io/docs/content-lake/backups), checked 24 October 2026
15. [Sanity Docs, History experience](https://www.sanity.io/docs/user-guides/history-experience), checked 24 October 2026
16. [Sanity, Plans that scale with you](https://www.sanity.io/pricing), checked 24 October 2026
17. [Sanity Docs, Plans and payments](https://www.sanity.io/docs/platform-management/plans-and-payments), checked 24 October 2026
18. [Sanity Docs, Schema validation and the Content Lake](https://www.sanity.io/docs/content-lake/schema-validation-and-the-content-lake), checked 24 October 2026
19. [Sanity Docs, Migrating your schema and content](https://www.sanity.io/docs/content-lake/schema-and-content-migrations), checked 24 October 2026
20. [Sanity Docs, Important considerations for schema and content migrations](https://www.sanity.io/docs/content-lake/important-considerations-for-schema-and-content-migrations), checked 24 October 2026
