All posts
Platform migrations

Contentful to Sanity Migration: Why Teams Switch and How

Contentful to Sanity migration: the pricing triggers that start it (content types, locales, space tiers) and how to convert rich text to Portable Text safely.

By Mehdi Hassan7 min read

The short answer

A Contentful to Sanity migration usually starts with a pricing trigger, such as hitting a content type, locale or space tier limit, and succeeds or fails on rich text. You export the space with Contentful's CLI, redesign the schema in Sanity Studio, convert Contentful rich text to Portable Text with a script, then import NDJSON and validate.

Key takeaways

  • Check whether a schema cleanup keeps you under Contentful's tier limit before you commit to moving.
  • Contentful rich text and Sanity Portable Text are both structured JSON, so conversion is node by node.
  • Embedded entries and inline links are where conversions break; inventory them first.
  • Sanity schemas live in code, so the migration is also the moment to fix your content model.
  • Import assets first and rewrite every asset reference, including those inside rich text.

Part of Headless CMS Migration: How to Move Between Contentful, Sanity, Payload and Strapi

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.

  1. 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.

  2. Design the Sanity schema

    Write document and object types in code. Merge redundant types and replace reference-to-section patterns with arrays of objects.

  3. Write the transform

    Map fields per type, convert rich text to Portable Text, turn links into Sanity references and reshape locales.

  4. Import assets

    Upload images and files to Sanity and keep a map of Contentful asset IDs to Sanity asset IDs.

  5. Import documents

    Write NDJSON with stable IDs and import it with Sanity's dataset import. Re-run until validation passes.

  6. Rewrite queries

    Replace Contentful API calls in the front end with GROQ queries that return the shape components expect.

  7. 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.

Frequently asked questions

Can I run Contentful and Sanity at the same time during migration?
Yes, and you should. Keep Contentful live while you build and test the Sanity import, then switch the front end once validation passes. Some teams move one content type at a time, with the front end reading from both CMSs. Keep Contentful read-only for a few weeks after cutover as a fallback.
Will my URLs change when moving from Contentful to Sanity?
They don't have to. URLs come from your front end and the slug field, so if you import slugs unchanged and keep routes the same, visitors and search engines see no difference. Validate that every published slug exists in Sanity before cutover, and redirect any you deliberately change.
How do I move Contentful environments to Sanity?
Sanity uses datasets rather than environments. Export your Contentful master environment for production content, and recreate staging or development as separate Sanity datasets if you need them. Schema changes in Sanity are code changes, so the role environments played for schema testing moves into your git workflow.
Is Sanity cheaper than Contentful?
It depends on your seats, usage and features, and both companies change their plans. Sanity prices around seats and usage, while Contentful's tiers are shaped by spaces, content types and locales. Model your actual numbers against both current pricing pages before deciding, and include migration cost in the comparison.
Do I need to rebuild my front end?
No. You rewrite the data-fetching layer to query Sanity with GROQ and return the same shape your components expect. Rich text rendering changes, because Portable Text needs its own renderer with components for your custom blocks. Pages, routes and design can stay exactly as they are.

· CEO & Design Director

Copywriter, designer and developer, and founder of Designify Studio. Over five years, 60+ projects across 14+ industries, for startups that have raised $300M+.

Want this done for you? Monthly retainer, $6,000.

Your ongoing design and development team, for products that keep shipping. $6,000, per month, flat fee. Book a free 30-minute call to see if it fits.

Schedule Call