← All playbooks
CMS migration

Sanity → Strapi

Export a Sanity dataset to NDJSON and asset files, then create Strapi content types and load entries through the API. strapi import only accepts a Strapi archive, and it deletes the destination first.

Typical timeline1 to 3 weeks20 to 50 hours active work
Statusneeds review
Source profileSanity dataset export docs reviewed 2026-11-03
Destination profileStrapi import docs and Strapi's Sanity migration guide reviewed 2026-11-03
Documentation date
Review window
Evidence3 sources
This route needs review.Use it as a planning baseline, then verify the dated sources and test with representative data before production.
Before you migrate

Should you make this move?

Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.

Current platform

Sanity

Reasons to stay
  • Content Lake, schema-as-code, and customizable Studio support sophisticated structured content
  • Editors get a purpose-built publishing workflow instead of raw files
Reasons to leave
  • GROQ, schema code, and custom editorial tooling demand engineering ownership
  • Themes, extensions, and content models add maintenance and portability constraints
New platform

Strapi

What gets better
  • An open-source headless CMS with a writable admin, REST, GraphQL, and a readable export archive
  • Editors get a purpose-built publishing workflow instead of raw files
What gets worse
  • Self-hosting, schema code, and custom importers become your problem when you leave a SaaS CMS
  • Themes, extensions, and content models add maintenance and portability constraints
Best of the move

Strapi: An open-source headless CMS with a writable admin, REST, GraphQL, and a readable export archive. This removes a major source-side concern: GROQ, schema code, and custom editorial tooling demand engineering ownership.

Worst of the move

What you lose: Content Lake, schema-as-code, and customizable Studio support sophisticated structured content. What you inherit: Self-hosting, schema code, and custom importers become your problem when you leave a SaaS CMS.

Jump to a section
01At a glance

Know the shape of the move.

Transfer outcome11 features audited
Transfer outcome distributionClean transfer: 0, Partial transfer: 5, Manual rebuild: 3, Not transferred: 3.
Clean0
Partial5
Manual3
Lost3
Mapping route9 of 9 fields have a destination path

This timeline assumes

  • You can export the Sanity dataset with a logged-in CLI or a read token.
  • Strapi is a new project with content types you are willing to design. It is not a drop-in for Sanity Studio.
  • You will load content through Strapi's API or a script. You will not point strapi import at the Sanity tarball.
  • The site already documents Sanity → Contentful and Sanity → WordPress.
02What transfers

What survives the move.

“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.

ItemOutcomeImpactWhat happensMitigation
Dataset archivepartialcriticalnpx sanity datasets export writes a gzipped tarball. Inside are data.ndjson, assets.json, images/, and files/. The default mode streams the dataset in one consistent read. Cursor mode is faster and can miss edits made while it runs.Use the default stream for the final export. Hash the tarball.
strapi importlostcriticalStrapi's import command restores a Strapi export. Schemas must already match. The command deletes existing data, including the database and uploads, before it loads the archive. Admin users are not restored. On shared cloud storage, deleted media records can delete the remote files.Do not run strapi import on a Sanity file. Build content types, then create entries.
Schemas and queriesmanualcriticalStrapi's own guide says Sanity schemas and GROQ do not carry over. Relationships have to be mapped again. The guide is a vendor tutorial, updated 14 June 2026, built around a sample project rather than a named customer.Draw the content types first. Rewrite frontend queries after a pilot entry exists.
AssetspartialcriticalBy default the Sanity export downloads every image and file asset, including ones nothing references. A 401, 403, or 404 is a warning. The export continues and can still finish. --no-assets drops binaries and strips references, so that file is not a complete copy. --raw keeps references and skips the rewrite.Export with binaries for the real move. Upload those files through Strapi and relink them.
DraftspartialhighSanity's --no-drafts flag exports only published documents. Without it, drafts are in the NDJSON and you must decide what Strapi should publish.Pick one. Do not import drafts as published pages by accident.
Document typespartialmediumThe --types flag limits the export to a comma-separated list. A partial export is easy to mistake for the whole dataset.For the archive, export every type. Filter later in the script.
Editorial history and presencelostmediumThe export is documents and files. Live presence, comments, and Studio configuration are not Strapi entries.Finish in-review documents before the final export.
RolesmanualhighSanity roles do not become Strapi admin roles. strapi import would not restore admin users even from a Strapi archive.Invite people in Strapi and set permissions before editors log in.
API quotamanualmediumSanity bills dataset exports against the API quota. Cursor mode uses more requests.Run the full export when you can watch the quota. Keep the tarball so you do not export twice.
Referenced library assetspartialmediumLinked Media Library assets are included at full size. A 404 often means the library asset was unlinked and later deleted while the dataset still has the asset document.Read the export warnings. Do not treat a 404 as a failed export, and do not ignore a missing binary you still reference.
Webhooks and GROQ listenerslosthighSanity webhooks and GROQ-powered clients keep calling Sanity until you change them.Point the site at Strapi only after the pilot content type renders.
03Field and feature mapping

Where each thing goes.

SourceDestinationMethodNotes
Dataset documentStrapi entrytransformOne NDJSON line becomes one create call after the content type exists.
Document typeCollection typemanualCreate the type before importing entries.
ReferenceStrapi relationtransformCreate targets first, then link them.
Image assetStrapi mediatransformUpload the file from the export and store the new id.
File assetStrapi filetransformSame upload path. Check the 404 warnings.
DraftDraft or skipmanualHonor --no-drafts or filter draft ids in the script.
SlugUID fieldtransformKeep the slug if the public URL should stay.
GROQ queryREST or GraphQL querymanualRewrite it. The export does not contain a query layer.
Project memberStrapi admin usermanualInvite them. Do not expect the archive to include passwords.
04Before you begin

Make the move recoverable.

Backup procedure

Create the source-of-truth backup

Keep a full Sanity tarball that is not your only copy of the content.

  1. Export with assets, published and drafts, default stream mode.
  2. Save the tarball outside the Strapi server.
  3. Read the asset warnings and list any 404 you still need.
  4. Export schemas from the Studio repo. They are code, not rows in data.ndjson.
  5. Leave Sanity available until Strapi renders the pilot.

Proof to capture: The tarball opens, data.ndjson has one line per document you expect, and the Studio repo is tagged.

Transformation · Strapi Content-Type Builder

Content types

Give Strapi a place to put Sanity documents.

  1. List document types from the Studio schema.
  2. Create a collection for each type you are keeping.
  3. Add relations only after both types exist.
  4. Do not run strapi import to do this. That command expects a Strapi archive and deletes current data.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

Transformation · A script against Strapi's API, or the transform in Strapi's Sanity guide

Entry load

Create entries and upload assets from the export.

  1. Read data.ndjson.
  2. Upload images and files first and keep an id map.
  3. Create entries. Then a second pass for relations.
  4. Strapi's guide also shows a community CLI for a sample project. Read it before you point it at production.

Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.

05Handle with care

The things most likely to hurt.

These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.

Pilot

strapi import deletes the project

criticalpossible likelihood

Someone passes the Sanity tarball to strapi import because the command is named import.

Consequence
Strapi wipes the database and uploads, then fails the schema check. Shared cloud buckets can lose files.
Mitigation
Use a script. Keep strapi import for Strapi-to-Strapi restores only.

Stop if: The command about to run is strapi import against a non-Strapi file.

Pilot

Relations point at missing ids

highlikely likelihood

Entries are created before the documents they reference.

Consequence
Links are empty and the site looks migrated.
Mitigation
Two passes: documents, then relations.

Stop if: A pilot reference is null.

Cutover

Drafts go live

highpossible likelihood

The NDJSON includes drafts and the script publishes everything.

Consequence
Unfinished documents are on the public API.
Mitigation
Filter drafts or export with --no-drafts for the public load.

Stop if: A known draft id is published.

06Precise timeline

Do the work in this order.

Estimate forOne Sanity dataset and a new Strapi project, with images in the export
Total elapsed1 to 3 weeks
Active work20 to 50 hours
BufferAdd time for Portable Text you must render, a large Media Library, or many GROQ clients.
01
Days 1 to 3Types and queries2 to 3 days
02
Days 3 to 5Tarball1 to 2 days
03
Days 5 to 12One type end to end4 to 8 days
04
Days 12 to 15Final export and DNS1 to 3 days
05
Days 15 to 22Keep Sanity7 days
  1. Days 1 to 3 · inventory

    Types and queries

    4 to 8 hours active2 to 3 days elapsedEditor sign-off waiting
    • List document types and the GROQ the site uses.
    • Decide drafts versus published.

    Depends on: Studio repo and a Strapi admin

    Stop / go checkpoint

    Export?

    Go when: Each public type has a Strapi destination.

    Stop when: A page type has no content type plan.

  2. Days 3 to 5 · backup

    Tarball

    2 to 6 hours active1 to 2 days elapsedAsset downloads waiting
    • Run datasets export in stream mode with assets.
    • Read 404 warnings.

    Depends on: CLI login or token

    Stop / go checkpoint

    Create types?

    Go when: data.ndjson and the asset folders exist.

    Stop when: The export used --no-assets and you still need files.

  3. Days 5 to 12 · pilot

    One type end to end

    10 to 24 hours active4 to 8 days elapsedAPI writes waiting
    • Create content types.
    • Load one type, its images, and its relations.
    • Render it on a preview site.

    Depends on: Tarball

    Stop / go checkpoint

    Load the rest?

    Go when: A reference and an image survive a reload.

    Stop when: You were about to run strapi import.

  4. Days 12 to 15 · cutover

    Final export and DNS

    4 to 10 hours active1 to 3 days elapsedFinal load waiting
    • Export again.
    • Load the delta.
    • Point the site at Strapi.

    Depends on: Pilot

    Stop / go checkpoint

    Strapi is the CMS?

    Go when: The public pages read Strapi and drafts you excluded are absent.

    Stop when: A relation is null on a top page.

  5. Days 15 to 22 · observe

    Keep Sanity

    2 to 4 hours active7 days elapsedEditors using Strapi waiting
    • Watch asset 404s and editor permissions.
    • Keep the Sanity project until rollback closes.

    Depends on: Cutover

    Stop / go checkpoint

    Leave Sanity read-only?

    Go when: Editors can publish in Strapi and the tarball is stored.

    Stop when: A missing asset is still only in Sanity.

07The point of change

Cut over with a way back.

Go live

Cutover

The site reads Strapi. Sanity is unchanged and still holds the dataset.

Recommended window: After a pilot type has already rendered on a preview host.

  1. Freeze Studio edits.
  2. Export once more in stream mode.
  3. Load entries, media, then relations.
  4. Switch the frontend.
  5. Check a draft did not publish.

Proof to capture: Top pages render from Strapi, relations resolve, and the tarball is archived.

Return to safety

Rollback

Point the site back at Sanity without deleting the dataset.

Deadline: Before you delete the Sanity project or the export tarball.

  1. Repoint the frontend at Sanity.
  2. Leave the Strapi project in place for the next attempt.
  3. Do not run strapi import to undo the load.
  4. Unfreeze Studio.
  5. Diff any Strapi edits you want to keep by hand.

Proof to capture: The public site reads Sanity again and the project was not deleted.

Rollback immediately when
  • strapi import was run against the wrong file
  • Relations are empty
  • Drafts are public
  • Assets 404
  • Editors cannot sign in to Strapi
08Verification report

Prove the migration worked.

Every blocking check must pass. Capture the evidence before cleanup begins.

0%
Interactive report preview0 / 8 checks passed
PassIDCheckMethodExpected resultEvidence
V-01BlockingNDJSON linesCount documents in data.ndjson against the dataset.The counts match the export mode you chose.Line count.
V-02BlockingWarning listRead export warnings and open five files from images/ and files/.Referenced binaries exist or are listed as accepted losses.Warning log.
V-03BlockingCollections existOpen Strapi admin.Each type you kept has a collection. strapi import was not used.Admin screenshot.
V-04BlockingPilot countCompare a type's document count with Strapi.They match, minus drafts you filtered.Count sheet.
V-05BlockingA referenceOpen an entry with a reference.The relation points at the right entry.Admin relation.
V-06BlockingUnpublished idsSearch Strapi for a known draft id.It is absent or still a draft.Search.
V-07BlockingOne pageRender a page from Strapi on preview.The HTML matches the fields you mapped.Preview URL.
V-08BlockingEditor loginSign in as a non-admin editor.They can edit what you allowed and nothing else.Role test.
09Post-migration cleanup

Remove the scaffolding safely.

Safe after: Editors have published in Strapi for a week and the tarball is stored off the server.

  1. Store the Sanity tarball and the Studio git tag.
  2. Revoke the export token.
  3. Remove the migration script's API token.
  4. Keep Sanity read-only until the rollback date.
  5. Delete unused Strapi upload tests.
  6. Rewrite any remaining GROQ client.
Sources and evidence

Verify against the primary material.

Platform behavior changes. Check these sources and the review dates above before executing a production migration.

  1. Sanity: export a datasetAccessed 2026-11-03
  2. Strapi: data importAccessed 2026-11-03
  3. Strapi: migrate from SanityAccessed 2026-11-03