Why do teams start a Contentful to Sanity migration?
Teams start a Contentful to Sanity migration mostly because of pricing tier boundaries: a content type cap, more locales, more seats or a jump to a larger space tier. The second reason is editing: Sanity's Studio is a React app you customize in code, which suits teams that want editorial tools shaped around their content.
Our research found that Contentful's tier jumps, such as the 25-content-type ceiling on a smaller space tier described in Pagepro's Contentful to Sanity write-up, push teams toward Sanity or Payload. Contentful's plans change, so confirm the current limits on its pricing page. For the general method behind any CMS-to-CMS move, start with our headless CMS migration hub.
Trigger | What you notice | Try first | Migrate if |
|---|---|---|---|
Content type limit | You can't add a new type | Merge types that differ by a flag | The model genuinely needs more types |
Locale limit | New market needs a language | Drop unused locales | Several new locales are planned |
Space tier jump | Next tier costs far more | Consolidate spaces or environments | Growth keeps crossing tiers |
Editor experience | Editors work around the UI | Configure the web app and apps | You need a custom studio |
Should you optimize Contentful or migrate to Sanity?
Optimize Contentful if a cleanup keeps you under the limit for the next year or two, and migrate if your roadmap will keep crossing tier boundaries. A migration costs weeks of engineering, so it has to save more than that over its lifetime.
Start with an audit: list every content type with its entry count, and every locale with its share of traffic. Types with few entries or that differ only by a field often merge cleanly. Our guide to platform price increases walks through the break-even math, and Contentful alternatives compares the other destinations, including Payload, which we cover in Contentful to Payload migration.
How do you migrate from Contentful to Sanity step by step?
You migrate from Contentful to Sanity by exporting the space, designing the Sanity schema, transforming entries and rich text, importing assets and documents as NDJSON, rewriting front-end queries and validating. Keep export, transform and import as separate steps so you can re-run the transform freely.
Export the Contentful space
Use the Contentful CLI's space export to save content types, entries, assets and locales as JSON. Include drafts if editors need them.
Design the Sanity schema
Write document and object types in code. Merge redundant types and replace reference-to-section patterns with arrays of objects.
Write the transform
Map fields per type, convert rich text to Portable Text, turn links into Sanity references and reshape locales.
Import assets
Upload images and files to Sanity and keep a map of Contentful asset IDs to Sanity asset IDs.
Import documents
Write NDJSON with stable IDs and import it with Sanity's dataset import. Re-run until validation passes.
Rewrite queries
Replace Contentful API calls in the front end with GROQ queries that return the shape components expect.
Validate and cut over
Check counts, references and rendered pages, freeze edits, run the final import and switch the front end.
Sanity also publishes open-source tooling for Contentful migrations. Check its current documentation for what it supports, and use it as a starting point rather than a finished migration, since your schema should improve in the move. For modeling principles, see content modeling for CMS migrations.
How do you convert Contentful rich text to Portable Text?
You convert Contentful rich text to Portable Text by walking Contentful's JSON node tree and emitting Portable Text blocks, spans, marks and custom objects. Both formats are structured JSON, so the conversion is precise, but every node type needs an explicit rule.
Contentful node | Portable Text equivalent | Notes |
|---|---|---|
paragraph | block with style normal | Text becomes spans |
heading-1 to heading-6 | block with style h1 to h6 | Check your Studio allows each style |
bold, italic, underline, code marks | Decorators on spans | Same names by convention |
hyperlink | link annotation in markDefs | Keep the href |
entry-hyperlink | internal link annotation with a reference | Resolve the ID through your lookup |
unordered and ordered lists | blocks with listItem and level | Nested lists set level |
embedded-asset-block | image object | Point at the re-uploaded Sanity asset |
embedded-entry-block | Custom object type | One custom type per embedded content type |
Why converting through HTML loses detail
A shortcut is to render Contentful rich text to HTML, then convert the HTML to Portable Text with Sanity's HTML-to-blocks tooling. It's quick for plain articles.
The problem is that HTML has no clean way to carry embedded entries and entry links. They either disappear or turn into markup you then have to parse back into references. If your content has embeds, convert the JSON tree directly. Use the HTML route only for simple text and test it on your most complex article first.
How do locales and references move from Contentful to Sanity?
Locales move by choosing Sanity's field-level or document-level localization pattern and reshaping Contentful's per-field locale values to match. References move with a two-pass import: create documents first, then patch in references using a Contentful-to-Sanity ID map.
Contentful stores each localized field as an object keyed by locale. In Sanity, field-level localization keeps one document with localized fields, which is close to Contentful's shape. Document-level localization creates one document per language linked together, which suits sites where translations differ in structure. Pick one before writing the transform, and check the current Sanity plugins for the pattern you choose. For references, keep the Contentful ID as a field on each Sanity document so the script is repeatable.
What changes for editors and developers after moving to Sanity?
After moving to Sanity, editors work in a Studio shaped around your content, and developers own the schema in code and query with GROQ. Both are gains, and both need a short adjustment period.
Pros
- Studio can be customized for each content type
- Schema lives in code, reviewed like any other change
- Portable Text makes rich content portable across channels
- Real-time collaboration on documents
Cons
- Developers must build and maintain the Studio
- GROQ is a new query language for the team
- Pricing has its own seat and usage boundaries
- Editors need retraining on the new UI
Check Sanity's current pricing for seats and usage, since every hosted CMS has its own tier boundaries. If you'd rather avoid hosted pricing altogether, compare a self-hosted option in Sanity to Payload migration and Contentful vs Sanity vs Payload vs Strapi.
