← All playbooks
CMS migration

Strapi → Sanity

Export Strapi as readable JSONL, then load Sanity with a migration script. Collection types become documents. Components become objects. Dynamic zones become arrays of named objects. Relations become references. Rich text becomes Portable Text. Upload assets first and keep Strapi documentId as a stable key.

Typical timeline15–40 business days70–160 hours active work
Statusneeds review
Source profileStrapi 5 data export docs reviewed 2026-10-31
Destination profileSanity importing-data docs and Strapi migration notes reviewed 2026-10-31
Documentation date
Review window
Evidence4 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

Strapi

Reasons to stay
  • 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
Reasons to leave
  • 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
New platform

Sanity

What gets better
  • Content Lake, schema-as-code, and customizable Studio support sophisticated structured content
  • Editors get a purpose-built publishing workflow instead of raw files
What gets worse
  • GROQ, schema code, and custom editorial tooling demand engineering ownership
  • Themes, extensions, and content models add maintenance and portability constraints
Best of the move

Sanity: Content Lake, schema-as-code, and customizable Studio support sophisticated structured content. This removes a major source-side concern: Self-hosting, schema code, and custom importers become your problem when you leave a SaaS CMS.

Worst of the move

What you lose: An open-source headless CMS with a writable admin, REST, GraphQL, and a readable export archive. What you inherit: GROQ, schema code, and custom editorial tooling demand engineering ownership.

Jump to a section
01At a glance

Know the shape of the move.

Transfer outcome12 features audited
Transfer outcome distributionClean transfer: 0, Partial transfer: 6, Manual rebuild: 4, Not transferred: 2.
Clean0
Partial6
Manual4
Lost2
Mapping route9 of 10 fields have a destination path

This timeline assumes

  • Strapi 5. You can run a readable export and reach the uploads files.
  • You have a Sanity project and dataset you can write with a token.
  • Strapi stays online until Sanity no longer needs Strapi media URLs.
  • The site already documents Contentful → Sanity and Strapi → Contentful as nearby routes.
  • You will write a script. There is no official one-click Strapi importer in Sanity.
02What transfers

What survives the move.

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

ItemOutcomeImpactWhat happensMitigation
Export archivepartialcriticalstrapi export includes entities, links, schemas, configuration, and assets. Admin users and API tokens are omitted. Default encryption hides the JSONL until you pass --no-encrypt.Export readable files. Keep a separate admin list.
Type mapmanualcriticalSanity’s Strapi notes map collection types to documents, single types to singleton documents, components to objects, and dynamic zones to arrays of named objects with one _type per __component.Write the Sanity schema first. List every __component value.
IdentifierspartialcriticalThe same notes say Strapi v5 documentId is the best stable source key for deterministic Sanity ids, especially with locales.Do not use auto ids. Derive Sanity _id from documentId.
RelationspartialcriticalStrapi relations become Sanity references. Import referenced documents first.Create-or-replace in dependency order. Fail if a reference is missing.
AssetspartialcriticalSanity’s skill says to upload assets to Sanity or the Media Library. Do not leave production on a legacy CDN.Upload first. Patch image fields with Sanity asset ids.
Rich textpartialhighBlocks, Markdown, HTML, or CKEditor JSON must become Portable Text. Storing raw HTML is a last resort.Convert a sample per type. Keep the source field in a hidden dump if you need to retry.
i18nmanualcriticalThe notes say localized Strapi v5 content often maps to document-level language fields and translation metadata, with a deterministic id pattern.Decide document-level versus field-level localization before the first import.
DraftspartialhighDecide whether unpublished Strapi rows become drafts in Sanity.Map status. Do not publish drafts.
Roles and tokenslosthighStrapi admin users and API tokens are not in the export. Sanity members are new accounts.Invite the team. Rotate tokens.
Webhooks and previewmanualhighStrapi webhooks do not become Sanity webhooks or presentation previews.Rebuild them.
Frontend queriesmanualcriticalGROQ and document ids replace REST or GraphQL you used on Strapi.Point a branch at the dataset. Run the suite.
History and scheduled publisheslostmediumStrapi history and schedules do not import as Sanity history.Archive what you need. Recreate future publishes.
03Field and feature mapping

Where each thing goes.

SourceDestinationMethodNotes
Collection typeSanity document typemanualSchema first.
Single typeSingleton document with a fixed idtransformOne document.
documentIdSanity _idtransformDeterministic.
ComponentObject typetransformKeep field names where you can.
Dynamic zoneArray of objects keyed by __componenttransformOne _type per component.
RelationReferencetransformImport targets first.
MediaSanity image or file assettransformUpload first.
Rich textPortable TexttransformDo not leave raw HTML on published docs.
Locale variantLanguage field or translated documenttransformPick one model.
Admin user or API tokenNo destinationunsupportedNot in the export.
04Before you begin

Make the move recoverable.

Backup procedure

Create the source-of-truth backup

Keep a readable Strapi export and a running Strapi you can point at again.

  1. Run a readable strapi export.
  2. Record type, locale, and asset counts.
  3. Hash the directory or archive.
  4. List every __component value.
  5. Keep a restore-tested Strapi backup.

Proof to capture: Export hash, component list, and a named restore owner.

Transformation · sanity schema plus the Strapi schemas folder

Sanity schema

Make documents, objects, and references that match the Strapi model.

  1. Read schemas/*.jsonl.
  2. Create document and object types.
  3. List dynamic-zone components.
  4. Deploy the schema before import.

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

Transformation · A script using createOrReplace or sanity dataset import --replace

ETL import

Load assets, then documents, then references.

  1. Upload assets.
  2. Transform entities to documents with stable ids.
  3. Convert rich text to Portable Text.
  4. Set references only after targets exist.
  5. Rerun until the dataset converges.

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.

Import

Auto ids instead of documentId

criticalpossible likelihood

Reruns create a second copy of every document.

Consequence
The dataset doubles. References point at leftovers.
Mitigation
Derive _id from documentId. Use createOrReplace.

Stop if: A rehearsal run creates duplicates for the same documentId.

Schema

Unknown __component

highpossible likelihood

A dynamic zone value has no Sanity _type.

Consequence
That block disappears or the import fails.
Mitigation
Inventory every __component before you write the schema.

Stop if: A production page relies on an unmapped component.

Assets

Published HTML still points at Strapi

criticalpossible likelihood

Portable Text or leftover HTML includes Strapi media URLs.

Consequence
Images die when you shut Strapi down.
Mitigation
Upload assets and rewrite URLs. Search the dataset for the old host.

Stop if: A high-traffic document still hotlinks Strapi.

06Precise timeline

Do the work in this order.

Estimate forOne Strapi 5 project moving into a Sanity dataset with a scripted import
Total elapsed15–40 business days
Active work70–160 hours
BufferAdd time for Portable Text conversion, many locales, or a large media library.
01
Days 1–5Inventory Strapi4–5 days
02
Days 5–8Readable export2–4 days
03
Days 8–28Schema and rehearsal import12–20 days
04
Days 28–35Freeze and switch the API3–5 days
05
Days 35–40Observe and close5–7 days
  1. Days 1–5 · inventory

    Inventory Strapi

    12–20 hours active4–5 days elapsedOwner review waiting
    • List types, components, locales, and media.
    • Choose Sanity document ids.

    Depends on: Strapi and Sanity admin access

    Stop / go checkpoint

    Export?

    Go when: The type and component map is signed.

    Stop when: A dynamic zone is unknown.

  2. Days 5–8 · backup

    Readable export

    6–12 hours active2–4 days elapsedExport runtime waiting
    • Export readable JSONL and assets.
    • Hash files.
    • Count rows.

    Depends on: inventory

    Stop / go checkpoint

    Write schema?

    Go when: JSONL opens and counts exist.

    Stop when: The archive is unreadable.

  3. Days 8–28 · pilot

    Schema and rehearsal import

    40–100 hours active12–20 days elapsedImport runtime waiting
    • Deploy Sanity schema.
    • Upload assets.
    • Import a sample, then the rest.
    • Point a frontend branch at Sanity.

    Depends on: backup

    Stop / go checkpoint

    Cut over?

    Go when: Counts, references, and Portable Text pass.

    Stop when: A required component or locale is wrong.

  4. Days 28–35 · cutover

    Freeze and switch the API

    8–18 hours active3–5 days elapsedFinal export waiting
    • Freeze Strapi publishes.
    • Re-export and rerun createOrReplace.
    • Point production at Sanity.

    Depends on: pilot

    Stop / go checkpoint

    Sanity is the CMS?

    Go when: Production reads Sanity only.

    Stop when: A production page still uses Strapi.

  5. Days 35–40 · observe

    Observe and close

    6–10 hours active5–7 days elapsedPublish cycle waiting
    • Watch previews and asset hosts.
    • Keep Strapi running.

    Depends on: cutover

    Stop / go checkpoint

    Close rollback?

    Go when: One publish cycle is clean.

    Stop when: Ids or assets regress.

07The point of change

Cut over with a way back.

Go live

Cutover

Make Sanity the only CMS the production frontend reads.

Recommended window: A weekday with CMS and frontend owners on call.

  1. Freeze Strapi publishing.
  2. Run a final export and createOrReplace import.
  3. Search the dataset for leftover Strapi hosts.
  4. Switch production environment variables.
  5. Publish one known document and confirm the live page.

Proof to capture: Production reads Sanity. Counts match. Assets do not use Strapi URLs you will cancel.

Return to safety

Rollback

Return the frontend to Strapi without losing Sanity-era edits you still want.

Deadline: Before you delete the Strapi project or drop the export.

  1. Point production back at Strapi.
  2. Export Sanity documents changed after cutover.
  3. Apply only reviewed documents back to Strapi if needed.
  4. Keep the Strapi export.

Proof to capture: Production reads Strapi again.

Rollback immediately when
  • Duplicate documentIds
  • Unmapped __component
  • Broken required asset
  • Frontend still mixed between APIs
  • Locale missing
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-01BlockingDeterministic _idRe-run the importer on a sample.Counts do not double. _id matches documentId.Id sheet.
V-02BlockingDocument countsCompare Strapi and Sanity per type and locale.Counts match or variances are signed.Count sheet.
V-03BlockingSanity hostsSearch documents for the old media host.Required images are Sanity assets.Asset report.
V-04BlockingDynamic zonesOpen a document that uses every __component.Each block has a Sanity _type.Component sample.
V-05BlockingProduction APIWatch network on a production build.No Strapi API requests.Network log.
V-06BlockingPortable TextRender a sample of converted bodies.Headings, links, and images survive.Render sample.
V-07BlockingLanguage documentsOpen each published locale for a sample type.Approved locale content.Locale sample.
V-08BlockingStrapi still servesKeep the project and a rollback env.You can point the frontend back.Rollback note.
09Post-migration cleanup

Remove the scaffolding safely.

Safe after: One publish cycle, no required Strapi media hosts, and seven stable days.

  1. Archive the export and importer log.
  2. Revoke Strapi tokens the frontend used.
  3. Keep Strapi running through the retention window.
  4. Rotate the Sanity write token used for import.
  5. Document dataset backup.
  6. Schedule the next schema and token review.
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. Strapi: data exportAccessed 2026-10-31
  2. Sanity: importing dataAccessed 2026-10-31
  3. Sanity agent toolkit: Strapi migration notesAccessed 2026-10-31
  4. Sanity agent toolkit: CMS migration skillAccessed 2026-10-31