Migration
How do you migrate from Contentful to Sanity?
Short answer
A Contentful to Sanity migration runs in three moves: convert the space, rewrite the schema as reviewed code, and import into a test dataset. Sanity's contentful-to-sanity tool exports the space and writes a schema file and a newline-delimited JSON (NDJSON) dataset (Sanity plugin page, October 2026). Version 5.0.0 drops Rich Text tables (converter source, October 2026), so count yours first.
- Official converter
- contentful-to-sanity 5.0.0, 13 March 2026Exports a space, writes a schema file and NDJSON; skips Rich Text tables, and imports one locale unless you run the separate schema and dataset commands with --intl multipleSources: Sanity (contentful-to-sanity Changelog, contentful-to-sanity source: contentfulEntryToSanityObject.ts, contentful-to-sanity source: command.ts)
- Contentful records
- 10,000 per environment on LiteLite's Starter Space allows 2 environments, each with its own 10,000 records of entries plus assets; Sanity counts documents across every datasetSources: Contentful (Pricing, Environments, Spaces)
- Sanity Growth
- $15 per seat a month, 25,000 documentsUnlimited content types and locales, 2 datasets; documents count across every dataset, drafts included, with no overage allowedSources: Sanity (Plans that scale with you); Sanity Docs (Plans and payments)
- Locale multiplier
- 100 documents in 3 languages become 400Document-level localization stores one document per language plus one translation.metadata document; field-level keeps oneSources: Sanity (@sanity/document-internationalization README); Sanity Docs (Localization)
Deploi’s call
Plan a Contentful to Sanity migration around what the converter won't do: Rich Text tables, JSON fields, archived entries, extra locales and every front-end query. Count documents the Sanity way, with drafts, every locale and every dataset, before you commit, because that number decides between Growth and Enterprise. Whether to leave Contentful at all is a separate decision; make it first.
PhaseMove from Contentful to Sanity in phases when
- Your Contentful organization runs several spaces or front ends, so each one can move, launch and be checked while the rest stays on Contentful.
- Your front end calls Contentful's Delivery or GraphQL APIs (application programming interfaces) throughout, because rewriting those queries in GROQ (Graph-Relational Object Queries) is the largest line item in Deploi's judgment, and a phase keeps each rewrite small enough to test.
- Editors keep publishing during the move, so you re-export and re-import each phase's content just before it launches.
MigrateMove a Contentful space to Sanity in one cutover when
- One space feeds one front end, so a single launch replaces every Contentful query.
- A test import matches your Contentful counts per content type, and the documents, with drafts and every locale, fit Sanity Growth's 25,000, or 50,000 with the $299 a month add-on, as of October 2026.
- Few Rich Text fields hold tables, because the converter drops tables and each one is rebuilt by hand.
What does Sanity give you to migrate from Contentful?
Sanity gives you two aids to migrate from Contentful, and neither runs the move end to end. The contentful-to-sanity command-line interface (CLI) turns a space export into a schema file and a newline-delimited JSON (NDJSON) dataset (Sanity plugin page, October 2026). Sanity's migration skill for AI (artificial intelligence) agents has a Contentful guide (Sanity on GitHub, October 2026), and Sanity calls the skill "agent-scaffolded, not fully automated" (Sanity Blog, What's new in August, October 2026).
| Aid | What it does | What it leaves to you |
|---|---|---|
| contentful-to-sanity CLI | Converts a space export into a schema file and an NDJSON dataset | Rich Text tables, JSON fields, archived entries and extra locales |
| Migration skill, Contentful guide | Helps an AI agent plan and write migration scripts | The decisions, running the scripts and checking the result |
Whether to leave Contentful at all is a decision separate from the migration, with the case for staying, and so are the points where each platform's price jumps to Enterprise. Salesforce says it completed the purchase on 1 September 2026 (Salesforce, October 2026); what Salesforce ownership changes for Contentful customers has its own answer.
How do Contentful content types, entries and references map to Sanity?
Each Contentful content type becomes a Sanity document type in code, each entry a document, and each Reference field a Sanity reference. The contentful-to-sanity converter also decides that slug editors become slug objects, Markdown becomes Portable Text unless you pass --keep-markdown, and JSON object fields become JSON strings (converter source, October 2026). When you rewrite the generated schema, merge types that were split to fit Contentful's 50-field cap (Contentful data model, October 2026).
| Contentful | Sanity | What changes |
|---|---|---|
| Content type | Document type in code | The schema lives in your repository |
| Entry | Document | A draft is a second document |
| Rich Text | Portable Text array | Tables need a table schema you declare |
| Asset | Image or file asset | Counts toward assets, not documents |
| Locale | Language field or array | Document-level or field-level per type |
| JSON object | String after conversion | Reshape into a typed object |
How does Contentful Rich Text become Portable Text?
Contentful Rich Text converts to Portable Text automatically for paragraphs, headings, lists, marks, links and embedded entries, but not for tables. Both are JSON trees: Contentful nests every node under one document root (Contentful rich text docs, October 2026), and Portable Text stores an array of blocks, each holding spans that carry marks (Sanity block content docs, October 2026). The contentful-to-sanity tool runs the @portabletext/contentful-rich-text-to-portable-text library, resolves embedded entries and entry links into Sanity references, turns horizontal rules into a break block, and drops table nodes (converter source, October 2026). Contentful's Rich Text field supports tables (Contentful data model), so search the export for table nodes before you convert, because a warning scrolls past in a long run.
The converter drops tables, so rebuild each one yourself. Sanity's Portable Text Editor has built-in table editing from Studio v6.6.0, which is off by default and binds to a table schema you declare (Sanity Docs, Configure the Portable Text Editor; Sanity Blog, What's new in August, October 2026); declare that schema and load each Contentful table into it. We built our own table block for deploi.ca, described in tables and citations in Portable Text. The Portable Text review covers the editor itself.
How should Contentful locales map to Sanity localization?
Map each Contentful content type to Sanity's document-level or field-level localization by one test: do editors publish locales on different days? Contentful publishes all of an entry's locales at once by default; publishing one locale alone needs locale-based publishing, sold only on specific plans (Contentful locale-based publishing, October 2026). Sanity's field-level localization keeps every language in one document but publishes all languages at once; document-level localization gives each language its own document that publishes independently (Sanity localization docs, October 2026). So per-locale publishing maps to document-level. Fallback locales don't migrate as a setting: in Sanity you write the fallback into each query with the coalesce() function of GROQ (Graph-Relational Object Queries), as the same docs show. The converter's --intl multiple mode writes the older __i18n_lang and __i18n_refs fields, which the current @sanity/document-internationalization plugin replaces with translation.metadata documents (converter source; plugin upgrade notes, October 2026). The Sanity localization review rates both approaches.
What happens to Contentful environments, and how many Sanity documents will you need?
Contentful environments become Sanity datasets only for development and risky schema changes; routine staging happens inside the production dataset, as drafts on Growth. Sanity reserves a separate dataset for development and for changes that rename, remove or restructure content (Sanity environments docs, October 2026). The counting rule flips too: Contentful gives every environment its own 10,000-record limit on Lite's Starter Space (Contentful environments FAQ; Contentful pricing, October 2026), while Sanity counts every stored document, drafts included, across all datasets, with no document overage on Growth (Sanity plans and payments, October 2026). So a full staging copy doubles your Sanity document count. Contentful's master alias maps to Sanity dataset aliases, which need Enterprise (Sanity environments docs).
| Sanity setup | Documents | Fits Growth |
|---|---|---|
| Field-level, production only | 8,000 | Yes, under 25,000 |
| Field-level, production plus a full staging copy | 16,000 | Yes |
| Document-level in 3 locales, production only | 32,000 | Only with the $299 a month quota add-on |
| Document-level in 3 locales, plus a staging copy | 64,000 | No, above 50,000 |
What happens to Contentful assets, URLs and dates in the move?
Contentful assets move as files, and URLs stay put unless you change them, because your front end owned the URLs, not Contentful. The converter points each asset at its Contentful URL through _sanityAsset (converter link source, October 2026), and Sanity's importer uploads every file it references, failing the run on a missing file unless you pass --allow-failing-assets (Sanity import docs, October 2026). So keep the Contentful space live until the import finishes, and archive an export made with the --download-assets flag of Contentful's command-line interface (CLI) (Contentful CLI docs, October 2026). Slug editors become Sanity slugs (converter source), so routes can stay identical.
Two traps hide in dates and history:
- Modified dates: the Sanity import sets
_updatedAtto the import time on every document that holds a reference (Sanity import docs), so keep Contentful's dates in a field of your own; keeping search traffic through a move to Sanity covers sitemaps and redirects. - History: Contentful's version history doesn't come with the export (Contentful CLI docs), so archive the export before cutover.
What replaces Contentful's roles, workflows, apps and webhooks?
None of Contentful's roles, workflows, apps or webhook secrets carry over to Sanity, so you rebuild each one. The export of Contentful's command-line interface (CLI) leaves workflows, apps and webhook credentials behind (Contentful CLI docs, October 2026); what a Contentful export leaves behind lists the rest. Here is what replaces each in Sanity, as of October 2026:
- Roles: Growth includes 5 roles and free viewer seats (Sanity pricing); custom roles need Enterprise (Sanity roles docs). See roles and permissions.
- Workflows: Sanity's Workflows product is in early access (Sanity Workflows docs); Comments and Tasks come with Growth (Sanity pricing).
- Scheduling: Growth has Scheduled drafts for one document at a time; Content Releases needs Enterprise (Sanity environments docs).
- Apps and UI (user interface) extensions: rebuild them as Sanity Studio plugins or custom inputs in your own React code.
- Webhooks: recreate them with a GROQ (Graph-Relational Object Queries) filter and a new secret (Sanity webhooks docs).
- Front-end queries: rewrite Contentful Delivery and GraphQL calls in GROQ. In Deploi's judgment this is the largest line item.
What is the step-by-step plan for a Contentful to Sanity migration?
A Contentful to Sanity migration runs as eight steps, and in Deploi's judgment the developer who owns the front end should own all of them. The plan uses Sanity's contentful-to-sanity tool, version 5.0.0 (changelog).
Moving a Contentful space to Sanity
- Audit the space. Count entries, assets and archived entries per environment, list locales, and find Rich Text tables and JSON fields.
- Choose localization per type. Document-level where locales publish on different days, field-level where they ship together.
- Export with drafts and assets. Include drafts, archived entries and assets (Contentful export docs).
- Rewrite the generated schema. Rewrite it as reviewed code, and declare a table schema if Rich Text used tables.
- Fix what the converter skipped. Rebuild tables and JSON fields, decide what archived entries become, reshape locale fields.
- Validate, then import to a test dataset. Run
sanity documents validateon the import file (Sanity migration docs), import, and compare counts per type. - Rewrite queries and test routes. Swap Contentful calls for GROQ (Graph-Relational Object Queries) queries, crawl old URLs, add redirects.
- Freeze and cut over. Freeze editing, re-export, import with
--replace, keep the imported ID list for rollback, then switch the front end.
Step 8 is how we ship large content packages with release scripts; choosing --replace or --missing covers its re-import.
Deploi’s point of view
- Where we disagree
- Two claims mislead buyers. The first is that Contentful localization is all-or-nothing per content type. Contentful's own docs describe locales enabled per field, and spaces with locale-based publishing publish one locale at a time; a team that relies on that and picks field-level localization in Sanity loses per-locale publishing. The second is the one-command migration. The official converter drops Rich Text tables, stores JSON object fields as strings, and imports archived entries as published documents.
- What we’ve seen
- We move content into Sanity as packages, never by hand. The first wave of deploi.ca's Shopify data section went in as one package of 406 documents and 159 images, signed off in October 2026, with check queries and a rollback by document ID list. Our decision pages go in as NDJSON with drafts-prefixed IDs, so nothing publishes before review, and with weak references, so import order doesn't matter. Our Portable Text carries a custom table block, and 229 of the 256 live answers in our Shopify FAQ hub use it, counted 24 October 2026. Those answers reach Sanity through our own Markdown to Portable Text importer; a converter that dropped tables would have stripped them from 229 of those 256 answers.
- What we'd tell you to do first
- Export the space with drafts and archived entries, then run three counts before anyone writes a schema: Rich Text fields that contain tables, JSON object fields, and archived entries. Each one is work the converter won't do for you. Then multiply entries by locales and by datasets to see which Sanity plan you are actually buying.
- What we refuse to promise
- A duration or an hours figure from a call. The drivers are the number of content types, the Rich Text features in use, the locale model, and how many front-end queries call Contentful directly; we scope from those counts.
When this answer changes
- Contentful changes Lite's locale, content type, environment or record quotas
- Sanity changes the Growth document quota, dataset count or seat price
- contentful-to-sanity adds Rich Text table support or changes its locale output to translation.metadata documents
- Sanity Workflows leaves early access or changes plan
- Sanity moves Content Releases or dataset aliases into Growth
- Sanity changes or retires its migration skill's Contentful guide
Sources
Every source opened and checked on 25 October 2026.
- Sanity, Plans that scale with you
- Contentful, Pricing
- Sanity Docs, Plans and payments
- Sanity Docs, Localization
- Sanity Docs, Development, staging, and production environments
- Sanity Docs, Import data
- Sanity, Contentful to Sanity plugin
- Sanity, contentful-to-sanity source: contentfulEntryToSanityObject.ts
- Sanity, contentful-to-sanity source: command.ts
- Sanity, @sanity/document-internationalization README
- Contentful, Environments
- Contentful, Import and export content with the Contentful CLI
- Contentful, Data model
- Contentful, What is rich text?
- Sanity Docs, Block content
- Contentful, Localization with Contentful
- Sanity, You're stuck at 49 of 50 content types. Again.
- Sanity, document-internationalization: Upgrade from v1
- Sanity Docs, Extending the Growth plan with paid add-ons
- Sanity Docs, How early access works (Workflows)
- Sanity, contentful-to-sanity source: contentfulToDataset.ts
- Contentful, Spaces
- Salesforce, Salesforce Signs Definitive Agreement to Acquire Contentful
- Sanity Docs, GROQ-powered webhooks
- Sanity, contentful-to-sanity Changelog
- Sanity Docs, Migrating your schema and content
- Sanity Docs, Roles
- Contentful, Locale-based publishing
- Sanity, contentful-to-sanity source: contentfulLinkToSanityReference.ts
- Sanity Docs, Configure the Portable Text Editor
- Sanity Blog, What's new in August
- Sanity, contentful-to-sanity source: createIntlFields.ts
- Sanity on GitHub, sanity-migration skill