{"id":19860,"date":"2025-10-24T13:16:35","date_gmt":"2025-10-24T11:16:35","guid":{"rendered":"https:\/\/pagepro.co\/blog\/?p=19860"},"modified":"2026-01-29T16:13:17","modified_gmt":"2026-01-29T15:13:17","slug":"migrate-contentful-to-sanity","status":"publish","type":"post","link":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/","title":{"rendered":"Migrate from Contentful to Sanity: A Complete Developer Guide"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\" id=\"is-contentful-migration-the-right-choice-for-you\">Is Contentful Migration the Right Choice for You?<\/h2>\n\n\n\n<p>While it has long been a go-to option for teams adopting headless CMS architectures, many organizations have now decided <strong><a href=\"https:\/\/pagepro.co\/blog\/top-headless-cms-agencies\/\">to migrate Contentful to Sanity or other setups<\/a><\/strong>.\u00a0<\/p>\n\n\n\n<p>Companies find <strong><a href=\"https:\/\/pagepro.co\/services\/contentful-migration-services\" target=\"_blank\" rel=\"noreferrer noopener\">Contentful\u2019s structure and pricing more and more restrictive<\/a><\/strong> as their projects scale.&nbsp;<\/p>\n\n\n\n<p>Limits on content types, rigid field definitions, and the absence of a real pay-as-you-go model often make it difficult to stay flexible without moving into costly enterprise plans.&nbsp;<\/p>\n\n\n\n<p>For teams focused on speed, content variety, and developer experience, <strong>these constraints are a major bottleneck<\/strong>. This is why they choose a shift toward more customizable and scalable alternatives like Sanity.<\/p>\n\n\n<figure class=\"wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube\"><div class=\"lyte-wrapper fourthree\" style=\"width:420px;max-width:100%;margin:5px;\"><div class=\"lyMe\" id=\"WYL_gly_IAFpQvg\"><div id=\"lyte_gly_IAFpQvg\" data-src=\"\/\/i.ytimg.com\/vi\/gly_IAFpQvg\/hqdefault.jpg\" class=\"pL\"><div class=\"tC\"><div class=\"tT\"><\/div><\/div><div class=\"play\"><\/div><div class=\"ctrl\"><div class=\"Lctrl\"><\/div><div class=\"Rctrl\"><\/div><\/div><\/div><noscript><a href=\"https:\/\/youtu.be\/gly_IAFpQvg\" rel=\"nofollow\"><img src=\"https:\/\/i.ytimg.com\/vi\/gly_IAFpQvg\/0.jpg\" alt=\"\" width=\"420\" height=\"295\" \/><br \/>Watch this video on YouTube<\/a><\/noscript><\/div><\/div><div class=\"lL\" style=\"max-width:100%;width:420px;margin:5px;\"><\/div><figcaption><\/figcaption><\/figure>\n\n\n<p>In this guide, I\u2019ll show you <strong>two easy ways<\/strong> of migrating your setup from Contentful to <strong><a href=\"https:\/\/pagepro.co\/services\/sanity-development\" target=\"_blank\" rel=\"noreferrer noopener\">Sanity<\/a><\/strong>. It\u2019s the same SEO-safe migration techniques we use in our projects. Before we start, I\u2019d like to discuss <strong>why businesses decide to move from Contentful<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"why-teams-migrate-from-contentful-to-sanity\">Why Teams Migrate from Contentful to Sanity<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"contentful-limitations\"><span class=\"underline-accent\">Contentful Limitations<\/span><\/h3>\n\n\n\n<p>Contentful\u2019s Starter and Lite plans are enough for most small-scale projects. However, once content structures start to grow, so do the constraints. The<strong> 25-content-type limit<\/strong> can be a serious obstacle for mid-sized or content-heavy applications. There\u2019s no pay-as-you-go option, so scaling up means jumping to a much higher plan instead of paying for incremental usage. Even then, the <strong>50-content-type limit<\/strong> might not be enough in some cases.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"301\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/contentful_spaces-1024x301.png\" alt=\"Contentful pricing comparison showing three workspace tiers \u2014 Starter Space, Lite Space, and Premium Spaces. The Starter Space and Lite Space options both include \u201cStart for free\u201d buttons, while Premium Spaces requires contacting sales. Below, a quota table highlights the difference in content type limits: 25 for Starter, 50 for Lite, and Custom for Premium.\" class=\"wp-image-19861\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/contentful_spaces-1024x301.png 1024w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/contentful_spaces-300x88.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/contentful_spaces-768x226.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/contentful_spaces-500x147.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/contentful_spaces-324x95.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/contentful_spaces.png 1399w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>On the development side,<strong> content models are bound by predefined field types, fixed validations, and a uniform UI<\/strong> that can\u2019t be customized for specific workflows. Rich text editing is also rigid. You can remove formatting options from the toolbar, but not extend or tailor the editor to support things like custom blocks, lists, or reference elements.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"why-sanity-is-a-better-fit\"><span class=\"underline-accent\">Why Sanity Is a Better Fit<\/span><\/h3>\n\n\n\n<p><a href=\"https:\/\/pagepro.co\/blog\/what-is-sanity\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Sanity<\/strong><\/a> addresses these pain points with a developer-first approach. Its schema system offers <strong>full control over content types, relationships, and validation rules<\/strong>. PortableText replaces Contentful\u2019s static rich text editor with <strong>a flexible structure that supports custom components and dynamic content<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"888\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_offers-1024x888.png\" alt=\"Sanity CMS pricing comparison chart showing three plans \u2014 Free, Growth, and Enterprise. The Free plan offers 20 seats, 2 user roles (Administrator and Viewer), and 2 datasets with 10k documents. The Growth plan costs $15 per seat\/month and expands to 50 seats, 5 user roles, 25k documents, and 4 GROQ-powered webhooks. The Enterprise plan includes custom pricing with unlimited seats, roles, and datasets. Add-ons include $999 per extra dataset and $299 for 50k additional documents.\" class=\"wp-image-19862\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_offers-1024x888.png 1024w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_offers-300x260.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_offers-768x666.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_offers-385x334.png 385w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_offers-324x281.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_offers.png 1094w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Real-time collaboration, customizable editing interfaces, and an API built around GROQ queries make it easier to build, query, and scale complex data models. Combined with a more accommodating pricing structure and strong integration with frameworks like Next.js, <strong>Sanity offers the agility that growing teams need without forcing trade-offs<\/strong>.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>We recommend Sanity because it gives both developers and content teams real freedom. You can shape the CMS around your product, not the other way around.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"130\" height=\"130\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/Rafal_dabrawski.png\" alt=\"\" class=\"wp-image-19652\"\/><\/figure>\n<cite>\u2014 <em>Rafa\u0142 D\u0105browski, Developer at Pagepro<\/em><\/cite><\/blockquote>\n\n\n\n<p>You can read about the differences between both CMSes in detail in our <a href=\"https:\/\/pagepro.co\/blog\/sanity-vs-contentful\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Headless CMS Guide: Sanity vs Contentful<\/strong><\/a><strong>.&nbsp;<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"pricing-comparison\"><span class=\"underline-accent\">Pricing Comparison<\/span><\/h3>\n\n\n\n<p>When comparing costs, Sanity stands out for its gradual, transparent pricing. It includes a forever-free tier and paid plans starting at around <strong>$15 per user\/month<\/strong> for the <strong>\u201cGrowth\u201d<\/strong> tier.&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"669\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/sanity_pricing-1024x669.png\" alt=\"Sanity pricing\" class=\"wp-image-19645\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/sanity_pricing-1024x669.png 1024w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/sanity_pricing-300x196.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/sanity_pricing-768x501.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/sanity_pricing-500x326.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/sanity_pricing-324x212.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/sanity_pricing.png 1184w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Sanity pricing as of October 2025<\/figcaption><\/figure>\n\n\n\n<p>Contentful does offer a free plan as well, but as soon as you require any meaningful team collaboration or commercial usage, the first paid tier jumps to around <strong>$300\/month (or higher)<\/strong> for the next level up. Additionally, if you cross 25 content types or have more than 10k of data, you might have to buy <strong>the Lite Space<\/strong>, which costs <strong>$850<\/strong>. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"663\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/contentful_pricing-1024x663.png\" alt=\"Contentful Pricing\" class=\"wp-image-19644\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/contentful_pricing-1024x663.png 1024w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/contentful_pricing-300x194.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/contentful_pricing-768x497.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/contentful_pricing-500x324.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/contentful_pricing-324x210.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/contentful_pricing.png 1059w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Contentful pricing as of October 2025<\/figcaption><\/figure>\n\n\n\n<p>That sharp leap between the free plan and the next level means that the decision to \u201cgo live\u201d with Contentful comes with a significant budget step that doesn\u2019t scale gently. Sanity allows a smoother transition from free to paid, whereas with Contentful, you may face a big budget move early on.<\/p>\n\n\n\n<p>Understanding the reasons to migrate Contentful to Sanity, we can look into how to make your transition safe and effective.<\/p>\n\n\n\n<div class=\"c-case-study-block js-sticky-wide\">\n    <div class=\"c-case-study-block__content\">\n        <div class=\"c-case-study-block__text\">\n                            <p class=\"c-case-study-block__subtitle\">GPnotebook<\/p>\n                                        <p class=\"c-case-study-block__title\">Migrating a 100K+ Page Medical Platform with Next.js &#038; Sanity<\/p>\n                                        <a href=\"https:\/\/pagepro.co\/case-studies\/gpnotebook\" class=\"c-case-study-block__button\" target=\"_blank\">\n                    READ THE CASE STUDY                <\/a>\n                    <\/div>\n                    <figure class=\"c-case-study-block__media\">\n                <img decoding=\"async\" class=\"c-case-study-block__image\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/gpnotebook_cs.png\" alt=\"A smartphone and a tablet display the About GPnotebook webpage, featuring details like sanity vs wordpress in its history, its purpose for GPs, and a photo of healthcare professionals talking\u2014all against a light blue background.\" \/>\n            <\/figure>\n            <\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"planning-a-safe-migration\">Planning a Safe Migration<\/h2>\n\n\n\n<p>Migrating from Contentful to Sanity is a structural change that can impact how your content, SEO, and integrations behave. Before running any scripts, <strong>decide which migration strategy makes the most sense for your projec<\/strong>t and learn about its risks.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"understand-the-risks\"><span class=\"underline-accent\">Understand the Risks<\/span><\/h3>\n\n\n\n<p>One of the first challenges is <strong>feature parity<\/strong>. Sanity may not have direct replacements for every Contentful plugin, so some marketplace extensions might need to be rebuilt or replaced with custom solutions. In many cases, this becomes an opportunity to clean up unused or outdated features. If a plugin supports a critical part of your workflow, plan for a rebuild.<\/p>\n\n\n\n<p>Next, consider the <strong>SEO and content integrity risks<\/strong>. Losing URL structures, internal links, or metadata during transfer can cause ranking drops and broken pages. <strong>Redirects, sitemaps, and structured data<\/strong> should be verified early in the process for a smooth transition.<\/p>\n\n\n\n<p>There\u2019s always the risk of <strong>data corruption or loss<\/strong>. Complex content models, broken relationships, and rich text formatting can all suffer during conversion, especially when dealing with localized data or large asset libraries. <\/p>\n\n\n\n<p>That\u2019s why <strong>migration testing should happen at multiple stages<\/strong>. First with automated checks (to confirm record counts, field types, and assets) and then <strong>with manual visual reviews<\/strong> to confirm that the content looks identical in Sanity Studio and on the frontend.<\/p>\n\n\n\n<p><\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"1024\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration-1024x1024.png\" alt=\"Step-by-step Contentful Migration Guide Preview\" class=\"wp-image-19739\" style=\"object-fit:cover;width:300px;height:300px\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration-1024x1024.png 1024w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration-300x300.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration-150x150.png 150w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration-768x768.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration-334x334.png 334w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration-324x324.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Square-Infographic-7-Steps-to-a-Successful-Contentful-Migration.png 1200w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure><\/div>\n\n\n<p><\/p>\n\n\n\n<div class=\"wp-block-buttons is-content-justification-center is-layout-flex wp-container-1 wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link has-text-align-center wp-element-button\" href=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/09\/Infographic-7-Steps-to-a-Successful-Contentful-Migration.pdf\">Download Our Checklist<\/a><\/div>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"choose-the-right-migration-path\"><span class=\"underline-accent\">Choose the Right Migration Path<\/span><\/h3>\n\n\n\n<p>Once you\u2019ve assessed the risks, decide how much of the existing structure should carry over. There are two approaches you can take:<\/p>\n\n\n\n<ul>\n<li><strong>Keep your existing structure: <\/strong>A fast and low-risk path where you move the current setup to Sanity without major refactoring. It\u2019s ideal if your main goal is to reduce costs or overcome Contentful\u2019s limitations without redesigning your data model.<\/li>\n\n\n\n<li><strong>Rebuild and migrate:<\/strong> A more strategic option where you use a custom script to redesign your content structure for long-term flexibility. This approach takes more time but gives you complete control over how your CMS evolves.<\/li>\n<\/ul>\n\n\n\n<p>If you\u2019re unsure, <strong>start by migrating the current setup using the official tool, confirm stability, and then iterate with schema improvements later<\/strong>. For small to mid-sized projects, this hybrid approach often completes in just a few days, as long as there are no missing dependencies or complex plugin integrations.<\/p>\n\n\n\n<div class=\"wp-block-code-mind-cta c-cta-block\" style=\"background-color:#00141F;color:#FFFFFF\"><div class=\"c-cta-block__content\"><p class=\"c-cta-block__title\">Not sure which migration path is better for you?<\/p><div class=\"c-cta-block__action\"><a href=\"https:\/\/pagepro.co\/contact\" class=\"c-cta-block__button ga-cta ga-cta-consultation theme-bg-3\">We can tell you<\/a><\/div><\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"option-1-migrate-using-the-official-contentful-to-sanity-cli\">Option 1: Migrate Using the Official Contentful-to-Sanity CLI<\/h2>\n\n\n\n<p>The fastest way to transfer your content from Contentful to Sanity is to <strong>use the official CLI tool<\/strong>, <a href=\"https:\/\/www.sanity.io\/plugins\/contentful-to-sanity\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>contentful-to-sanity<\/strong><\/a>. It exports all your data, converts it into Sanity\u2019s format, and even generates matching schema files. <strong>You can have a working Sanity Studio in minutes<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-1-get-your-contentful-credentials\"><span class=\"underline-accent\">Step 1: Get Your Contentful Credentials<\/span><\/h3>\n\n\n\n<p>To run the script, you\u2019ll need <strong>three API keys<\/strong> from your Contentful account:<\/p>\n\n\n\n<ul>\n<li><strong>Space ID<\/strong> &#8211; easiest to copy from your project URL, e.g. <code>https:\/\/app.contentful.com\/spaces\/[projectURL]\/views\/entries<\/code> or find it under <strong>Settings \u2192 Space Settings \u2192 General Settings<\/strong><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"351\" height=\"540\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id.png\" alt=\"Contentful dashboard menu showing navigation under Space settings. The dropdown includes options such as Locales, General settings, Users, API keys, CMA tokens, and Webhooks. These settings are essential for retrieving credentials and configuring environments before running a Contentful-to-Sanity migration.\" class=\"wp-image-19863\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id.png 351w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id-195x300.png 195w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id-217x334.png 217w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id-324x498.png 324w\" sizes=\"(max-width: 351px) 100vw, 351px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"839\" height=\"240\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id_2.png\" alt=\"Contentful General Settings screen showing space details. The section indicates that the space was created by Jakub Dakowicz on 28 Aug 2025 and displays the Space ID field (8g9vl6174izn) with a copy button beside it. This ID is required for migration or API configuration when connecting Contentful to Sanity or other platforms.\" class=\"wp-image-19864\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id_2.png 839w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id_2-300x86.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id_2-768x220.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id_2-500x143.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/space_id_2-324x93.png 324w\" sizes=\"(max-width: 839px) 100vw, 839px\" \/><\/figure>\n\n\n\n<ul>\n<li><strong>Content Delivery API token<\/strong> &#8211; create one in <strong>Settings \u2192 API Keys.<\/strong><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"337\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token-1024x337.png\" alt=\"Contentful API key settings page showing the process of generating an Access token. The form includes fields for Name, Description, Space ID (8g9vl6174izn), and a Content Delivery API access token, which is required to connect Contentful with external applications such as Sanity or Next.js during a CMS migration. The \u201cSave\u201d button is visible in the top-right corner for finalizing token creation.\" class=\"wp-image-19865\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token-1024x337.png 1024w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token-300x99.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token-768x253.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token-1536x506.png 1536w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token-500x165.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token-324x107.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_delivery_token.png 1862w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<ul>\n<li><strong>Content Management API (CMA) token<\/strong> &#8211; generate it under<strong> Account Settings \u2192 <\/strong><a href=\"https:\/\/app.contentful.com\/account\/profile\/cma_tokens\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>CMA Tokens<\/strong><\/a>. You can then generate a new token by clicking the top-right button:<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"298\" height=\"108\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/button.png\" alt=\"Close-up view of the blue \u201cCreate personal access token\u201d button in Contentful\u2019s account settings. Clicking this button generates a CMA (Content Management API) token, which allows developers to authenticate programmatically during a CMS migration or API integration process.\" class=\"wp-image-19866\"\/><\/figure>\n\n\n\n<p><strong>Tip:<\/strong> Set your token to expire in <strong>1\u201330 days<\/strong>. One day is fine for testing, thirty for production<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"644\" height=\"423\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/token_expiry.png\" alt=\"Contentful pop-up window titled \u201cCreate personal access token.\u201d The dialog shows fields for Token name (filled with \u201cDemo preview\u201d) and Expiration date (set to 15 Oct 2025), with action buttons labeled Cancel and Generate. This screen is used to create a CMA token (Content Management API) that enables programmatic access for automations or migrations, such as transferring data from Contentful to Sanity.\" class=\"wp-image-19867\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/token_expiry.png 644w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/token_expiry-300x197.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/token_expiry-500x328.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/token_expiry-324x213.png 324w\" sizes=\"(max-width: 644px) 100vw, 644px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-2-run-the-migration-script\"><span class=\"underline-accent\">Step 2: Run the Migration Script<\/span><\/h3>\n\n\n\n<p>Now that you have your keys, run the CLI command:<\/p>\n\n\n\n<p><code>npx contentful-to-sanity@latest -s &lt;space-id&gt; -t &lt;cma-token&gt; -a &lt;Content Delivery API - access token&gt; .\/output-path<\/code><\/p>\n\n\n\n<p>If tokens are correct, you\u2019ll see logs of the export. A 401 error means <strong>one of the keys is invalid<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"609\" height=\"903\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/script.png\" alt=\"Terminal view of the Contentful-to-Sanity migration script exporting content types, entries, and assets with successful completion logs\" class=\"wp-image-19868\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/script.png 609w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/script-202x300.png 202w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/script-225x334.png 225w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/script-324x480.png 324w\" sizes=\"(max-width: 609px) 100vw, 609px\" \/><figcaption class=\"wp-element-caption\">CLI output showing successful export of Contentful data for migration to Sanity.<\/figcaption><\/figure>\n\n\n\n<p>After the script finishes, you\u2019ll find these files in your output directory:<\/p>\n\n\n\n<ul>\n<li><strong>contentful.json<\/strong> and <strong>contentful.published.json<\/strong> &#8211; raw exports from Contentful<\/li>\n\n\n\n<li><strong>dataset.ndjson<\/strong> &#8211; data formatted for Sanity import<\/li>\n\n\n\n<li><strong>schema.ts <\/strong>&#8211; auto-generated schema matching your Contentful models<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"452\" height=\"142\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/files.png\" alt=\"Project folder showing migration output files: contentful.json, contentful.published.json, dataset.ndjson, and schema.ts.\" class=\"wp-image-19869\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/files.png 452w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/files-300x94.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/files-324x102.png 324w\" sizes=\"(max-width: 452px) 100vw, 452px\" \/><figcaption class=\"wp-element-caption\">Files generated by the Contentful-to-Sanity migration script, ready for import into Sanity.<\/figcaption><\/figure>\n\n\n\n<p>At this stage, you\u2019re about halfway done. The data is ready, and the schema is mapped.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-3-create-your-sanity-project\"><span class=\"underline-accent\">Step 3: Create Your Sanity Project<\/span><\/h3>\n\n\n\n<p>If you don\u2019t already have a Sanity account, sign up at <a href=\"https:\/\/www.sanity.io\/login\/sign-up.\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">https:\/\/www.sanity.io\/login\/sign-up.<\/a> Then create a new, clean project:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">npm create sanity@latest \\\n  --template clean \\\n  --create-project \"Your Project Name\" \\\n  --dataset production \\\n  --output-path my-sanity-project\n<\/code><\/pre>\n\n\n\n<p>Log in when prompted, select <strong>\u201cCreate new project\u201d<\/strong>. Choose your organization, and follow the prompts. You should have a clean Sanity project.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"446\" height=\"341\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_1.png\" alt=\"Sanity project folder structure in VS Code showing configuration files and schemaTypes directory.\" class=\"wp-image-19872\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_1.png 446w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_1-300x229.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_1-437x334.png 437w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_1-324x248.png 324w\" sizes=\"(max-width: 446px) 100vw, 446px\" \/><figcaption class=\"wp-element-caption\">Sanity project directory after initialization, ready for importing migrated content.<\/figcaption><\/figure>\n\n\n\n<p>Open your sanity.config and sanity.cli files to check your <strong>project ID<\/strong> and <strong>dataset<\/strong>. You\u2019ll need them soon.<\/p>\n\n\n\n<p>Now, copy the generated schema.ts into your schemaTypes directory with an updated naming:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"376\" height=\"87\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/naming.png\" alt=\"Sanity schemaTypes folder containing index.ts and schema-contentful.ts used for CMS schema setup.\" class=\"wp-image-19874\" style=\"aspect-ratio:4.32183908045977;width:630px;height:auto\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/naming.png 376w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/naming-300x69.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/naming-324x75.png 324w\" sizes=\"(max-width: 376px) 100vw, 376px\" \/><\/figure>\n\n\n\n<p>And update your index.ts:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">import { types } from \".\/schema-contentful\";\nexport const schemaTypes = [...types];\n<\/code><\/pre>\n\n\n\n<p><br>That will allow us to add new schemas later without impacting the auto-generated file. Now, it\u2019s time to <strong>test our new CMS system<\/strong> with the schema. <\/p>\n\n\n\n<p>Run <code>npm run dev<\/code> to preview your new Studio. You should see something like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"487\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view-1024x487.png\" alt=\"Sanity Studio dashboard displaying imported content types after migration from Contentful.\" class=\"wp-image-19875\" style=\"aspect-ratio:2.102669404517454;width:899px;height:auto\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view-1024x487.png 1024w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view-300x143.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view-768x366.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view-1536x731.png 1536w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view-500x238.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view-324x154.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_view.png 1893w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>On the left, you can see all of the content types, which will be empty for now. Remember their names, before we move on to the next step: <strong>data migration<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-4-import-your-content\"><span class=\"underline-accent\">Step 4: Import Your Content<\/span><\/h3>\n\n\n\n<p>Copy <code>dataset.ndjson<\/code> into your project and run:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">npx sanity dataset import .\/dataset.ndjson<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"324\" height=\"393\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_2.png\" alt=\"Sanity project directory in VS Code showing dataset.ndjson file prepared for import.\" class=\"wp-image-19881\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_2.png 324w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_2-247x300.png 247w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/my_sanity_project_2-275x334.png 275w\" sizes=\"(max-width: 324px) 100vw, 324px\" \/><figcaption class=\"wp-element-caption\"><code>dataset.ndjson<\/code> file ready for import into Sanity Studio<\/figcaption><\/figure>\n\n\n\n<p>Select your dataset (default: <code>production<\/code>).<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"549\" height=\"117\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/import.png\" alt=\"Terminal output showing successful import of 54 documents and assets into Sanity production dataset.\" class=\"wp-image-19882\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/import.png 549w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/import-300x64.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/import-500x107.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/import-324x69.png 324w\" sizes=\"(max-width: 549px) 100vw, 549px\" \/><figcaption class=\"wp-element-caption\">Successful data import<\/figcaption><\/figure>\n\n\n\n<p>After the import completes, your entries <strong>will appear inside Sanity Studio<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"712\" height=\"773\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_studio.png\" alt=\"Sanity Studio showing imported content types and documents after migration from Contentful.\" class=\"wp-image-19883\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_studio.png 712w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_studio-276x300.png 276w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_studio-308x334.png 308w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sanity_studio-324x352.png 324w\" sizes=\"(max-width: 712px) 100vw, 712px\" \/><figcaption class=\"wp-element-caption\">Sanity Studio view<\/figcaption><\/figure>\n\n\n\n<p>Next, you should update your fetching methods in the NextJS app to use <strong>the new data source<\/strong>.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-5-update-your-queries\"><span class=\"underline-accent\">Step 5: Update Your Queries<\/span><\/h3>\n\n\n\n<p>Replace your Contentful GraphQL API with <strong>Sanity\u2019s GROQ-based client<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">\/\/ Sanity client configuration\nconst client = createClient({\n  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID || 'your-project-id',\n  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production',\n  apiVersion: '2024-01-01',\n  useCdn: process.env.NODE_ENV === 'production',\n  token: process.env.SANITY_API_TOKEN,\n});\n<\/code><\/pre>\n\n\n\n<p>We need to update our API calls and queries. Since Contentful uses GraphQL queries, let\u2019s start by <strong>converting them into GroQ queries<\/strong>. If you\u2019re struggling with this part, consider using an AI to help.<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">const PAGE_GROQ_FIELDS = `\n_id,\n _type,\n \"slug\": slug.current,\n internalName,\n pageName,\n seo {\n   _id,\n   _type,\n   name,\n   title,\n   description,\n   \"ogImage\": image.asset->url,\n   noIndex,\n   noFollow\n },\n topSection[] {\n   _id,\n   _type,\n   _ref,\n   internalName,\n   \/\/ Component CTA\n   ...(_type == \"componentCta\" => {\n     headline,\n     subline,\n     ctaText,\n     targetPage-> {\n       \"slug\": slug.current\n     },\n     urlParameters,\n     colorPalette\n   }),\n   \/\/ Component Duplex\n   ...(_type == \"componentDuplex\" => {\n     containerLayout,\n     headline,\n     bodyText,\n     ctaText,\n     targetPage-> {\n       \"slug\": slug.current\n     },\n     image {\n       asset-> {\n         url,\n         metadata {\n           dimensions\n         }\n       },\n       alt\n     },\n     imageStyle,\n     colorPalette\n   }),\n<\/code><\/pre>\n\n\n\n<p>The<strong> -&gt; <\/strong>sign is<strong> very important here<\/strong>. When a field is a reference to another document, by default, we only get the reference <code>id<\/code>. To get real values from this reference and resolve it, you need to use <code>-&gt; syntax<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-6-update-the-code\"><span class=\"underline-accent\">Step 6: Update the Code<\/span><\/h3>\n\n\n\n<p>After updating the queries, we can now focus on updating the code where we fetch the data. Right now, <strong>your setup is fetching all posts and uses the GraphQL, Contentful URL, and access tokens<\/strong>.&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">\nxport async function getAllPosts(isDraftMode: boolean): Promise&lt;any[]> {\n const entries = await fetchGraphQL(\n   `query {\n     pageCollection(where: { slug_exists: true }, preview: ${\n       isDraftMode ? \"true\" : \"false\"\n     }) {\n       items {\n         ${POST_GRAPHQL_FIELDS}\n       }\n     }\n   }`,\n   isDraftMode,\n );\n return extractPostEntries(entries);<\/code><\/pre>\n\n\n\n<p>Let\u2019s update it to use <strong>our new Sanity client and query<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">\/\/ Get all pages\nexport async function getAllPosts(isDraftMode: boolean): Promise&lt;any[]> {\n const query = groq`*[_type == \"page\" &amp;&amp; defined(slug.current)] | order(_createdAt desc) {\n   ${PAGE_GROQ_FIELDS}\n }`\n  const entries = await client.fetch(query)\n return extractPageEntries(entries)\n}\n<\/code><\/pre>\n\n\n\n<p>Update all of the places <strong>where you fetch the data from Contenful to use Sanity<\/strong>. <\/p>\n\n\n\n<p>You can still use GraphQL in this scenario. Go to <a href=\"https:\/\/www.sanity.io\/docs\/content-lake\/graphql\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>the official Sanity docs<\/strong><\/a> and see how to implement that. It requires a few additional steps, like deploying the API on every change to the schema.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-7-update-the-richtext\"><span class=\"underline-accent\">Step 7: Update the Richtext<\/span><\/h3>\n\n\n\n<p>Now we need to update the <code>richtext<\/code> component. <strong>Contentful uses its own logic to store and render<\/strong> the <code>richtext<\/code> markdown. Sanity has a different approach and its own pattern to keep this kind of data using the <code>PortableText<\/code> component, <strong>which we will apply now<\/strong>.&nbsp;<\/p>\n\n\n\n<p>We need to define the components we want to use for each block type and replace the old Markdown renderer.&nbsp; <strong>Update the markdown component<\/strong> to something similar to this one:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">import Image from \"next\/image\";\nimport { PortableText } from \"@portabletext\/react\";\nimport { PortableTextBlock } from \"@portabletext\/types\";\n\n\ninterface SanityImage {\n _id: string;\n url: string;\n alt?: string;\n caption?: string;\n asset: {\n   _ref: string;\n   _type: string;\n };\n}\n\n\ninterface SanityContent {\n _type: string;\n _key: string;\n children?: Array&lt;{\n   _type: string;\n   _key: string;\n   text: string;\n   marks?: string[];\n }>;\n markDefs?: Array&lt;{\n   _key: string;\n   _type: string;\n   href?: string;\n }>;\n asset?: SanityImage;\n url?: string;\n alt?: string;\n}\n\n\ninterface PortableTextContent {\n _type: \"block\" | \"image\" | \"code\" | \"table\" | \"list\";\n _key: string;\n children?: SanityContent[];\n markDefs?: SanityContent[];\n asset?: SanityImage;\n url?: string;\n alt?: string;\n code?: string;\n language?: string;\n}\n\n\nfunction SanityImageComponent({ value }: { value: SanityImage }) {\n return (\n   &lt;div className=\"my-6\">\n     &lt;Image\n       src={value.url}\n       alt={value.alt || \"\"}\n       width={800}\n       height={600}\n       className=\"rounded-lg\"\n       style={{\n         width: \"100%\",\n         height: \"auto\",\n       }}\n     \/>\n     {value.caption &amp;&amp; (\n       &lt;p className=\"text-sm text-gray-600 mt-2 text-center italic\">\n         {value.caption}\n       &lt;\/p>\n     )}\n   &lt;\/div>\n );\n}\n\n\nfunction CodeBlock({ value }: { value: { code: string; language?: string } }) {\n return (\n   &lt;pre className=\"bg-gray-100 p-4 rounded-lg overflow-x-auto my-6\">\n     &lt;code className={`language-${value.language || \"text\"}`}>\n       {value.code}\n     &lt;\/code>\n   &lt;\/pre>\n );\n}\n\n\nconst components = {\n types: {\n   image: SanityImageComponent,\n   code: CodeBlock,\n },\n marks: {\n   link: ({ children, value }: { children: React.ReactNode; value: { href: string } }) => (\n     &lt;a\n       href={value.href}\n       target=\"_blank\"\n       rel=\"noopener noreferrer\"\n       className=\"text-blue-600 hover:text-blue-800 underline\"\n     >\n       {children}\n     &lt;\/a>\n   ),\n   strong: ({ children }: { children: React.ReactNode }) => (\n     &lt;strong className=\"font-bold\">{children}&lt;\/strong>\n   ),\n   em: ({ children }: { children: React.ReactNode }) => (\n     &lt;em className=\"italic\">{children}&lt;\/em>\n   ),\n   code: ({ children }: { children: React.ReactNode }) => (\n     &lt;code className=\"bg-gray-100 px-1 py-0.5 rounded text-sm font-mono\">\n       {children}\n     &lt;\/code>\n   ),\n },\n block: {\n   h1: ({ children }: { children: React.ReactNode }) => (\n     &lt;h1 className=\"text-3xl font-bold mb-4 mt-8\">{children}&lt;\/h1>\n   ),\n   h2: ({ children }: { children: React.ReactNode }) => (\n     &lt;h2 className=\"text-2xl font-bold mb-3 mt-6\">{children}&lt;\/h2>\n   ),\n   h3: ({ children }: { children: React.ReactNode }) => (\n     &lt;h3 className=\"text-xl font-bold mb-2 mt-4\">{children}&lt;\/h3>\n   ),\n   h4: ({ children }: { children: React.ReactNode }) => (\n     &lt;h4 className=\"text-lg font-bold mb-2 mt-4\">{children}&lt;\/h4>\n   ),\n   normal: ({ children }: { children: React.ReactNode }) => (\n     &lt;p className=\"mb-4 leading-relaxed\">{children}&lt;\/p>\n   ),\n   blockquote: ({ children }: { children: React.ReactNode }) => (\n     &lt;blockquote className=\"border-l-4 border-gray-300 pl-4 my-4 italic text-gray-700\">\n       {children}\n     &lt;\/blockquote>\n   ),\n },\n list: {\n   bullet: ({ children }: { children: React.ReactNode }) => (\n     &lt;ul className=\"list-disc list-inside mb-4 space-y-1\">{children}&lt;\/ul>\n   ),\n   number: ({ children }: { children: React.ReactNode }) => (\n     &lt;ol className=\"list-decimal list-inside mb-4 space-y-1\">{children}&lt;\/ol>\n   ),\n },\n listItem: {\n   bullet: ({ children }: { children: React.ReactNode }) => (\n     &lt;li className=\"ml-4\">{children}&lt;\/li>\n   ),\n   number: ({ children }: { children: React.ReactNode }) => (\n     &lt;li className=\"ml-4\">{children}&lt;\/li>\n   ),\n },\n};\n\n\nexport function MarkdownSanity({ content }: { content: PortableTextBlock[] }) {\n return (\n   &lt;div className=\"prose prose-lg max-w-none\">\n     &lt;PortableText value={content} components={components} \/>\n   &lt;\/div>\n );\n}\n<\/code><\/pre>\n\n\n\n<p>Find all places where you\u2019ve used the <strong>Markdown<\/strong> and change it from this:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">&lt;div className=\"prose\">\n&lt;Markdown content={post.content} \/>\n&lt;\/div><\/code><\/pre>\n\n\n\n<p>To this:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">&lt;div className=\"prose\">\n&lt;MarkdownSanity content={post.content} \/>\n&lt;\/div><\/code><\/pre>\n\n\n\n<p>Congratulations! This should be enough <strong>to transfer the CMS and convert your UI to use Sanity data and <code>richtext<\/code><\/strong>.&nbsp;Now all that\u2019s left to do is to focus on adding extra features, plugins like preview, which, in my opinion, is a must-have for editors. <\/p>\n\n\n\n<p>Follow <a href=\"https:\/\/www.sanity.io\/docs\/visual-editing\/configuring-the-presentation-tool\"><strong>this guide<\/strong><\/a> to set it up. You can also plan out the revalidation of data. Choose either Webhooks and <a href=\"https:\/\/www.sanity.io\/learn\/course\/controlling-cached-content-in-next-js\/path-based-revalidation\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>Revalidate API<\/strong><\/a> or use <a href=\"https:\/\/www.sanity.io\/docs\/content-lake\/live-content-api\" target=\"_blank\" rel=\"noreferrer noopener nofollow\"><strong>Sanity Live API<\/strong><\/a>.<\/p>\n\n\n\n<div class=\"c-newsletter-block-acf\">\n    <p class=\"c-newsletter-block-acf__title c-newsletter__header\">\n        EXPERT INSIGHTS, FRICTIONLESSLY DELIVERED!    <\/p>\n    <p class=\"c-newsletter-block-acf__desc c-newsletter__header\">\n        Get curated tech news from Pagepro delivered to your inbox every month.    <\/p>\n    <form method=\"post\" class=\"c-newsletter-block-acf__form js-newsletter-form c-newsletter__action\" name=\"newsletter-block-form\">\n        <input name=\"newsletter-email\" id=\"newsletter-email\" type=\"text\" class=\"c-newsletter-block-acf__input js-newsletter-input\" placeholder=\"Company Email\" \/>\n        <input name=\"newsletter-campaign\" id=\"newsletter-campaign\" type=\"hidden\" value=\"\" \/>\n        <div class=\"c-newsletter-block-acf__group\">\n            <input name=\"consent\" id=\"consent\" type=\"checkbox\" class=\"js-newsletter-consent\" \/>\n            <label class=\"c-newsletter-block-acf__label\" for=\"consent\">I accept the <a href=\"https:\/\/pagepro.co\/privacy-policy\">Privacy Policy<\/a> and agree to process my personal data by Pagepro for marketing purposes.<\/label>\n        <\/div>\n        <input type=\"submit\" class=\"c-newsletter-block-acf__button button js-newsletter-sub ga-newsletter-form-content\" value=\"Sign up\" \/>\n        <p class=\"theme-size-1 js-message-valid is-hidden is-invalid\"><\/p>\n    <\/form>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"option-2-create-your-own-cms-migration-script\">Option 2: Create Your Own CMS Migration Script<\/h2>\n\n\n\n<p>If the official CLI tool doesn\u2019t work for your project,&nbsp; for example, because you use nonstandard plugins or highly customized content types,&nbsp; <strong>you can create your own migration script<\/strong>.<\/p>\n\n\n\n<p>This path takes longer, but it gives you full control over field mapping, schema design, and data validation.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>The custom-script path takes longer, but it gives full control. Once your schemas are right, mapping is easy and the output clean and predictable.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"130\" height=\"130\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/08\/Rafal_dabrawski.png\" alt=\"\" class=\"wp-image-19652\"\/><\/figure>\n\n\n\n<p><\/p>\n<cite><em>Rafa\u0142 D\u0105browski, Developer at Pagepro<\/em><br><\/cite><\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-1-export-data-from-contentful\"><span class=\"underline-accent\">Step 1: Export Data from Contentful<\/span><\/h3>\n\n\n\n<p>Start by <strong>installing the official CLI<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">npm install -g contentful-cli\n<\/code><\/pre>\n\n\n\n<p><strong>Log in<\/strong> to your Contentful account:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">contentful login<\/code><\/pre>\n\n\n\n<p>Then <strong>export<\/strong> your space data:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">contentful space export --space-id &lt;your-space-id><\/code><\/pre>\n\n\n\n<p>You should see a similar screen to the one from <code>contentful-to-sanity<\/code> script:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"393\" height=\"521\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/initiate_2.png\" alt=\"Terminal output showing successful export of 17 content types, 53 entries, and 21 assets from Contentful.\" class=\"wp-image-19884\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/initiate_2.png 393w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/initiate_2-226x300.png 226w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/initiate_2-252x334.png 252w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/initiate_2-324x430.png 324w\" sizes=\"(max-width: 393px) 100vw, 393px\" \/><figcaption class=\"wp-element-caption\">CLI output confirming successful export of Contentful data for migration to Sanity.<\/figcaption><\/figure>\n\n\n\n<p>This command generates a <code>.json<\/code> file containing your full space data. It includes <code>contentTypes<\/code>, entries, assets, and localization information.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"213\" height=\"332\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_types.png\" alt=\"JSON export structure from Contentful showing content types, entries, assets, and locales.\" class=\"wp-image-19885\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_types.png 213w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_types-192x300.png 192w\" sizes=\"(max-width: 213px) 100vw, 213px\" \/><\/figure>\n\n\n\n<p>For custom migrations, <strong>ignore <code>contentTypes<\/code><\/strong> and focus on <code>entries<\/code>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"205\" height=\"140\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/sys.png\" alt=\"JSON object from Contentful export showing metadata, sys, and fields used for data mapping.\" class=\"wp-image-19886\"\/><\/figure>\n\n\n\n<p>Each entry\u2019s <code>sys<\/code> object tells you what content type it belongs to, along with its ID and relationships. You\u2019ll use this data to <strong>identify how fields map to your new schema.<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"313\" height=\"127\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_type.png\" alt=\"Contentful JSON showing contentType link structure with sys, linkType, and ID fields.\" class=\"wp-image-19887\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_type.png 313w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/content_type-300x122.png 300w\" sizes=\"(max-width: 313px) 100vw, 313px\" \/><\/figure>\n\n\n\n<p>Now we see that we have a type of page. We can analyze the fields we have here.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"317\" height=\"365\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/fields.png\" alt=\"Contentful JSON fields for a pricing page showing localized content and linked entries.\" class=\"wp-image-19888\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/fields.png 317w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/fields-261x300.png 261w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/fields-290x334.png 290w\" sizes=\"(max-width: 317px) 100vw, 317px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-2-define-your-sanity-schemas\"><span class=\"underline-accent\">Step 2: Define Your Sanity Schemas<\/span><\/h3>\n\n\n\n<p>In a fresh Sanity project, start by creating schemas that match your data model.&nbsp; Below is an example of defining <strong>SEO metadata<\/strong> and <strong>Page<\/strong> document types.<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">\/\/ ts-nocheck\nimport { defineField, defineType } from \"sanity\";\nexport const seoType = defineType({\n   type: \"document\",\n   name: \"seo\",\n   title: \"SEO meta tags\",\n   fields: [\n     defineField({\n       name: \"name\",\n       type: \"string\",\n       title: \"Internal name\",\n       hidden: false,\n       validation: (Rule) => Rule.required(),\n     }),\n     defineField({\n       name: \"title\",\n       type: \"string\",\n       title: \"SEO title\",\n       hidden: false,\n       description: \"This will override the page title in search engine results\",\n     }),\n     defineField({\n       name: \"description\",\n       type: \"string\",\n       title: \"Description\",\n       hidden: false,\n       description: \"This will be displayed in search engine results\",\n     }),\n     defineField({\n       name: \"image\",\n       type: \"image\",\n       title: \"Image\",\n       hidden: false,\n     }),\n     defineField({\n       name: \"noIndex\",\n       type: \"boolean\",\n       title: \"Hide page from search engines (noindex)\",\n       hidden: false,\n     }),\n     defineField({\n       name: \"noFollow\",\n       type: \"boolean\",\n       title: \"Exclude links from search rankings (nofollow)\",\n       hidden: false,\n     }),\n   ],\n });\n\n\nexport const pageType = defineType({\n   type: \"document\",\n   name: \"page\",\n   title: \"Page\",\n   description: \"Our landing pages\",\n   fields: [\n     defineField({\n       name: \"title\",\n       type: \"string\",\n       title: \"Title\",\n       validation: (Rule) => Rule.required(),\n     }),\n     defineField({\n       name: \"slug\",\n       type: \"slug\",\n       title: \"Slug\",\n       validation: (Rule) => Rule.required(),\n     }),\n     defineField({\n       name: \"seo\",\n       type: \"reference\",\n       title: \"SEO metadata\",\n       to: [{ type: \"seo\" }],\n     }),\n     defineField({\n       name: \"content\",\n       type: \"array\",\n       title: \"Content\",\n       of: [\n         { type: \"topicBusinessInfo\" },\n         { type: \"topicProduct\" },\n         { type: \"componentProductTable\" },\n       ],\n     }),\n   ],\n });\n<\/code><\/pre>\n\n\n\n<p>Then register these schemas in your main <strong>configuration file<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">import { pageType, seoType } from \".\/schema.fresh\";\n\nexport const schemaTypes = [ pageType, seoType ]<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"step-3-write-your-migration-script\"><span class=\"underline-accent\">Step 3: Write Your Migration Script<\/span><\/h3>\n\n\n\n<p>Let\u2019s start working on the script. It will map the data from the old system into sanity schemas and generate an <code>.ndjson<\/code> for easy import.&nbsp;<\/p>\n\n\n\n<p>First, we need to read the <code>input<\/code> file and then find the correct data from the <code>input<\/code> file and extract the required data. Then <strong>we map this data to a new schema<\/strong> and generate the <code>NDJson<\/code> file that we can use for import. <\/p>\n\n\n\n<p>The final script might look similar to this:<\/p>\n\n\n\n<pre class=\"wp-block-code-mind-code c-code\"><code class=\"javascript\">#!\/usr\/bin\/env node\n\nconst fs = require('fs');\nconst path = require('path');\n\nconst CONFIG = {\n inputFile: 'contentful-export-8g9vl6174izn-master-2025-10-15T10-17-31.json',\n outputFile: 'pages-migrated.ndjson',\n primaryLocale: 'en-US'\n};\n\nlet contentfulData;\ntry {\n const filePath = path.join(__dirname, CONFIG.inputFile);\n console.log(`\ud83d\udcd6 Reading Contentful export from: ${filePath}`);\n  contentfulData = JSON.parse(\n   fs.readFileSync(filePath, 'utf8')\n );\n} catch (error) {\n console.error('\u274c Error reading Contentful export file:', error.message);\n process.exit(1);\n}\n\n\/\/ Filter for page entries\nconst pageEntries = contentfulData.entries.filter(entry =>\n entry.sys?.contentType?.sys?.id === 'page'\n);\n\nconsole.log(`\ud83d\udcc4 Found ${pageEntries.length} page entries`);\n\nfunction convertPageToSanity(contentfulPage) {\n const fields = contentfulPage.fields;\n const primaryLocale = CONFIG.primaryLocale;\n  \/\/ Map Contentful fields to Sanity schema\n const sanityPage = {\n   _type: 'page',\n   _id: contentfulPage.sys.id,\n   title: fields.pageName?.[primaryLocale] || fields.internalName?.[primaryLocale] || 'Untitled',\n   slug: {\n     _type: 'slug',\n     current: fields.slug?.[primaryLocale] || 'untitled'\n   }\n };\n\n\n \/\/ Handle SEO reference if it exists\n if (fields.seo?.[primaryLocale]) {\n   sanityPage.seo = {\n     _type: 'reference',\n     _ref: fields.seo[primaryLocale].sys.id\n   };\n }\n  \/\/  TODO: Handle content later \/ add sections etc\n sanityPage.content = [];\n\n\n return sanityPage;\n}\n\n\/\/ Convert all page entries\nconst sanityPages = pageEntries.map(convertPageToSanity);\n\n\/\/ Generate NDJSON output\nconst ndjsonContent = sanityPages\n .map(page => JSON.stringify(page))\n .join('\\n');\n\n\/\/ Write to output file\nconst outputPath = path.join(__dirname, CONFIG.outputFile);\ntry {\n fs.writeFileSync(outputPath, ndjsonContent);\n console.log(`\u2705 Migration completed!`);\n console.log(`\ud83d\udcc4 Converted ${sanityPages.length} pages`);\n console.log(`\ud83d\udcc1 Output file: ${outputPath}`);\n} catch (error) {\n console.error('\u274c Error writing output file:', error.message);\n process.exit(1);\n}\n\n\/\/ Display sample of converted data\nif (sanityPages.length > 0) {\n console.log('\\n\ud83d\udccb Sample converted page:');\n console.log(JSON.stringify(sanityPages[0], null, 2));\n  \/\/ Show field mapping summary\n console.log('\\n\ud83d\udcca Field mapping summary:');\n console.log('Contentful \u2192 Sanity');\n console.log('pageName \u2192 title');\n console.log('slug \u2192 slug.current');\n console.log('seo \u2192 seo (reference)');\n}\n\n<\/code><\/pre>\n\n\n\n<p>Here\u2019s the sample output:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"656\" height=\"324\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/output_2.png\" alt=\"Terminal output showing successful Contentful-to-Sanity migration and converted page data sample.\" class=\"wp-image-19889\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/output_2.png 656w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/output_2-300x148.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/output_2-500x247.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/output_2-324x160.png 324w\" sizes=\"(max-width: 656px) 100vw, 656px\" \/><\/figure>\n\n\n\n<p>And the <code>ndjson<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"940\" height=\"183\" src=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/ndjson2.png\" alt=\"NDJSON file showing migrated Contentful pages with titles, slugs, and IDs ready for Sanity import.\" class=\"wp-image-19890\" srcset=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/ndjson2.png 940w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/ndjson2-300x58.png 300w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/ndjson2-768x150.png 768w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/ndjson2-500x97.png 500w, https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/ndjson2-324x63.png 324w\" sizes=\"(max-width: 940px) 100vw, 940px\" \/><\/figure>\n\n\n\n<p>Now that you have a working base, focus on creating and refining your schemas. Once you\u2019re satisfied with their structure, adjust your script to map each field correctly, as shown in the example. <strong>Getting the schemas right is the most time-consuming &#8211; and important &#8211; part of this migration.<\/strong> Once that\u2019s done, mapping the data is straightforward.<\/p>\n\n\n\n<p>When your output looks correct, import it into Sanity and <strong>continue with the next steps outlined the step 4 in Option 1<\/strong>: updating rich text rendering and adding any plugins you need.&nbsp;<\/p>\n\n\n\n<p>Some teams use alternative methods, like web scraping, to extract content. If that approach fits your project better, it\u2019s worth exploring.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h2>\n\n\n\n<p>Migration is a process that rewards preparation. With a clear plan, backups, and a careful approach, <strong>anybody can migrate Contentful to Sanity<\/strong>.<\/p>\n\n\n\n<p>How long it takes depends entirely on your goals. If you\u2019re simply moving to Sanity for better flexibility and are happy with your current data model, <strong>the process can be very quick<\/strong>. But if you\u2019re rebuilding schemas or adding new features, treat it as an opportunity to refine your CMS structure for the long term.<\/p>\n\n\n\n<p>Above all, <strong>never skip testing<\/strong>. Validate your redirects, metadata, and content relationships before the final switch. A few extra rounds of automated and manual checks can save you from downtime, broken links, and SEO drops.<\/p>\n\n\n\n<p>Are you having problems with your CMS migration? Don\u2019t hesitate to ask us for help!<\/p>\n\n\n\n<div class=\"wp-block-code-mind-cta c-cta-block\" style=\"background-color:#00141F;color:#FFFFFF\"><div class=\"c-cta-block__content\"><p class=\"c-cta-block__title\">Having problems with your CMS migration?<\/p><div class=\"c-cta-block__action\"><a href=\"https:\/\/pagepro.co\/contact\" class=\"c-cta-block__button ga-cta ga-cta-consultation theme-bg-3\">Ask for our help<\/a><\/div><\/div><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"sources\">Sources<\/h2>\n\n\n\n<ul>\n<li><a href=\"https:\/\/www.sanity.io\/plugins\/contentful-to-sanity\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Official Contentful to Sanity Plugin<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.sanity.io\/blog\/contentful-to-sanity\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">How to Easily Migrate from Contentful to Sanity<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.sanity.io\/contentful-vs-sanity\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">Sanity vs Contentful<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"read-more\">Read More<\/h2>\n\n\n\n<ul>\n<li><a href=\"https:\/\/pagepro.co\/blog\/why-sanity-is-the-best-cms-for-the-healthcare-industry\/\" target=\"_blank\" rel=\"noreferrer noopener\">Why Sanity is the Best CMS for the Healthcare Industry<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/pagepro.co\/blog\/what-is-headless-cms\/\" target=\"_blank\" rel=\"noreferrer noopener\">What Is Headless CMS and How Does It Work?<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/pagepro.co\/blog\/top-5-best-headless-cms-platforms\/\" target=\"_blank\" rel=\"noreferrer noopener\">5 Best Headless CMS Platforms in 2025: Reviews &amp; Comparisons<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq\">FAQ<\/h2>\n\n\n\n<div class=\"schema-faq wp-block-yoast-faq-block\"><div class=\"schema-faq-section\" id=\"faq-question-1763123991612\"><strong class=\"schema-faq-question\">Why Do Companies Migrate Contentful to Sanity?<\/strong> <p class=\"schema-faq-answer\">Teams move from Contentful to Sanity for greater flexibility, customization, and predictable costs. Contentful\u2019s model isn\u2019t easily customizable either.\u00a0<br\/>Sanity, on the other hand, lets developers define their own content models, add custom input components, and collaborate in real time. It works better for growing teams that need control without the limits of a closed ecosystem.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1763124007738\"><strong class=\"schema-faq-question\">Why is Contentful So Expensive?<\/strong> <p class=\"schema-faq-answer\">The biggest issue with Contentful\u2019s pricing is the steep jump between the free and paid tiers. The free plan is generous for individual developers, but for teams only a few content types and users are allowed. The next available plan costs roughly $300\/month, which comes with additional purchases like different types of spaces.\u00a0<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1763124023755\"><strong class=\"schema-faq-question\">Is Sanity CMS Free?<\/strong> <p class=\"schema-faq-answer\">Yes. Sanity offers a free forever plan suitable for small projects, MVPs, and personal sites. Paid plans start at around $15 per user\/month, adding collaboration features, increased API limits, and additional datasets.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1763124044595\"><strong class=\"schema-faq-question\">How Long Does it Take to Migrate Contentful to Sanity?<\/strong> <p class=\"schema-faq-answer\">Migration time depends on your setup. Using the official CLI tool, a simple project can be moved in a day or two. For larger projects with custom content models, the process can take a few weeks.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1763124054958\"><strong class=\"schema-faq-question\">What\u2019s the Difference Between Using the CLI Tool and Writing a Custom Script for Migration?<\/strong> <p class=\"schema-faq-answer\">The CLI tool is fast and automated. It exports data from Contentful, converts it into Sanity\u2019s format, and even generates schema files. Writing a custom migration script takes longer but gives you total control. You can redesign schemas, fine-tune data mapping, and decide exactly how relationships and fields are structured.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1763124062087\"><strong class=\"schema-faq-question\">Can I Migrate Assets, Such as Images and PDFs, from Contentful to Sanity?<\/strong> <p class=\"schema-faq-answer\">Yes. The official CLI handles assets automatically, while a custom script requires you to include them manually using the assets array and Sanity\u2019s upload API.<\/p> <\/div> <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Learn the two ways to migrate your setup from Contentful to Sanity .<\/p>\n","protected":false},"author":16,"featured_media":19900,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[660,673],"tags":[70,316],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v21.3 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>How to Migrate Contentful to Sanity in 2025<\/title>\n<meta name=\"description\" content=\"Migrate Contentful to Sanity without losing data or SEO. Follow our 2025 guide with CLI commands, code examples, and schema setup tips.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Migrate Contentful to Sanity in 2025\" \/>\n<meta property=\"og:description\" content=\"Migrate Contentful to Sanity without losing data or SEO. Follow our 2025 guide with CLI commands, code examples, and schema setup tips.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/\" \/>\n<meta property=\"og:site_name\" content=\"Pagepro\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/thisispagepro\" \/>\n<meta property=\"article:published_time\" content=\"2025-10-24T11:16:35+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-01-29T15:13:17+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/Migrate-from-Contentful-to-Sanity-A-Complete-Developer-Guide.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"582\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Jakub Dakowicz\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jakub Dakowicz\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/\"},\"author\":{\"name\":\"Jakub Dakowicz\",\"@id\":\"https:\/\/pagepro.co\/blog\/#\/schema\/person\/66e00cf32ef7d2d1b010523eff380caf\"},\"headline\":\"Migrate from Contentful to Sanity: A Complete Developer Guide\",\"datePublished\":\"2025-10-24T11:16:35+00:00\",\"dateModified\":\"2026-01-29T15:13:17+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/\"},\"wordCount\":2811,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/pagepro.co\/blog\/#organization\"},\"keywords\":[\"for CTO\",\"webdev\"],\"articleSection\":[\"For CTO\",\"Sanity\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#respond\"]}]},{\"@type\":[\"WebPage\",\"FAQPage\"],\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/\",\"url\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/\",\"name\":\"How to Migrate Contentful to Sanity in 2025\",\"isPartOf\":{\"@id\":\"https:\/\/pagepro.co\/blog\/#website\"},\"datePublished\":\"2025-10-24T11:16:35+00:00\",\"dateModified\":\"2026-01-29T15:13:17+00:00\",\"description\":\"Migrate Contentful to Sanity without losing data or SEO. Follow our 2025 guide with CLI commands, code examples, and schema setup tips.\",\"breadcrumb\":{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#breadcrumb\"},\"mainEntity\":[{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763123991612\"},{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124007738\"},{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124023755\"},{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124044595\"},{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124054958\"},{\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124062087\"}],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/pagepro.co\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Blog\",\"item\":\"https:\/\/pagepro.co\/blog\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Migrate from Contentful to Sanity: A Complete Developer Guide\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/pagepro.co\/blog\/#website\",\"url\":\"https:\/\/pagepro.co\/blog\/\",\"name\":\"Pagepro\",\"description\":\"Frictionless Next.js, Expo &amp; Sanity Development Blog\",\"publisher\":{\"@id\":\"https:\/\/pagepro.co\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/pagepro.co\/blog\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/pagepro.co\/blog\/#organization\",\"name\":\"Pagepro\",\"url\":\"https:\/\/pagepro.co\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/pagepro.co\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2020\/08\/logo_pagepro-b66d228a1e-1.png\",\"contentUrl\":\"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2020\/08\/logo_pagepro-b66d228a1e-1.png\",\"width\":440,\"height\":200,\"caption\":\"Pagepro\"},\"image\":{\"@id\":\"https:\/\/pagepro.co\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/thisispagepro\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/pagepro.co\/blog\/#\/schema\/person\/66e00cf32ef7d2d1b010523eff380caf\",\"name\":\"Jakub Dakowicz\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/pagepro.co\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5e0855c6f563f4e1a4a53206089ce0cc?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5e0855c6f563f4e1a4a53206089ce0cc?s=96&d=mm&r=g\",\"caption\":\"Jakub Dakowicz\"},\"description\":\"Jakub is the Chief Technology Officer at Pagepro, where he leads technical strategy and oversees the architecture of complex web platforms built with Next.js and headless CMS solutions. With nearly nine years at Pagepro and over five years leading the engineering team, he has been instrumental in shaping the company\u2019s architectural standards, development workflows, and scalability practices. Jakub focuses on building robust, composable systems that balance performance, maintainability, and long-term business flexibility. He drives technical decision-making across projects, ensuring that solutions are not only modern, but strategically aligned with client growth.\",\"sameAs\":[\"https:\/\/www.linkedin.com\/in\/jakub-dakowicz-939838102\/\"],\"url\":\"https:\/\/pagepro.co\/blog\/author\/jakub_dakowicz\/\"},{\"@type\":\"Question\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763123991612\",\"position\":1,\"url\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763123991612\",\"name\":\"Why Do Companies Migrate Contentful to Sanity?\",\"answerCount\":1,\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Teams move from Contentful to Sanity for greater flexibility, customization, and predictable costs. Contentful\u2019s model isn\u2019t easily customizable either.\u00a0<br\/>Sanity, on the other hand, lets developers define their own content models, add custom input components, and collaborate in real time. It works better for growing teams that need control without the limits of a closed ecosystem.\",\"inLanguage\":\"en-US\"},\"inLanguage\":\"en-US\"},{\"@type\":\"Question\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124007738\",\"position\":2,\"url\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124007738\",\"name\":\"Why is Contentful So Expensive?\",\"answerCount\":1,\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"The biggest issue with Contentful\u2019s pricing is the steep jump between the free and paid tiers. The free plan is generous for individual developers, but for teams only a few content types and users are allowed. The next available plan costs roughly $300\/month, which comes with additional purchases like different types of spaces.\u00a0\",\"inLanguage\":\"en-US\"},\"inLanguage\":\"en-US\"},{\"@type\":\"Question\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124023755\",\"position\":3,\"url\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124023755\",\"name\":\"Is Sanity CMS Free?\",\"answerCount\":1,\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes. Sanity offers a free forever plan suitable for small projects, MVPs, and personal sites. Paid plans start at around $15 per user\/month, adding collaboration features, increased API limits, and additional datasets.\",\"inLanguage\":\"en-US\"},\"inLanguage\":\"en-US\"},{\"@type\":\"Question\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124044595\",\"position\":4,\"url\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124044595\",\"name\":\"How Long Does it Take to Migrate Contentful to Sanity?\",\"answerCount\":1,\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Migration time depends on your setup. Using the official CLI tool, a simple project can be moved in a day or two. For larger projects with custom content models, the process can take a few weeks.\",\"inLanguage\":\"en-US\"},\"inLanguage\":\"en-US\"},{\"@type\":\"Question\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124054958\",\"position\":5,\"url\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124054958\",\"name\":\"What\u2019s the Difference Between Using the CLI Tool and Writing a Custom Script for Migration?\",\"answerCount\":1,\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"The CLI tool is fast and automated. It exports data from Contentful, converts it into Sanity\u2019s format, and even generates schema files. Writing a custom migration script takes longer but gives you total control. You can redesign schemas, fine-tune data mapping, and decide exactly how relationships and fields are structured.\",\"inLanguage\":\"en-US\"},\"inLanguage\":\"en-US\"},{\"@type\":\"Question\",\"@id\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124062087\",\"position\":6,\"url\":\"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124062087\",\"name\":\"Can I Migrate Assets, Such as Images and PDFs, from Contentful to Sanity?\",\"answerCount\":1,\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Yes. The official CLI handles assets automatically, while a custom script requires you to include them manually using the assets array and Sanity\u2019s upload API.\",\"inLanguage\":\"en-US\"},\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How to Migrate Contentful to Sanity in 2025","description":"Migrate Contentful to Sanity without losing data or SEO. Follow our 2025 guide with CLI commands, code examples, and schema setup tips.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/","og_locale":"en_US","og_type":"article","og_title":"How to Migrate Contentful to Sanity in 2025","og_description":"Migrate Contentful to Sanity without losing data or SEO. Follow our 2025 guide with CLI commands, code examples, and schema setup tips.","og_url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/","og_site_name":"Pagepro","article_publisher":"https:\/\/www.facebook.com\/thisispagepro","article_published_time":"2025-10-24T11:16:35+00:00","article_modified_time":"2026-01-29T15:13:17+00:00","og_image":[{"width":1024,"height":582,"url":"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2025\/10\/Migrate-from-Contentful-to-Sanity-A-Complete-Developer-Guide.png","type":"image\/png"}],"author":"Jakub Dakowicz","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Jakub Dakowicz","Est. reading time":"17 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#article","isPartOf":{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/"},"author":{"name":"Jakub Dakowicz","@id":"https:\/\/pagepro.co\/blog\/#\/schema\/person\/66e00cf32ef7d2d1b010523eff380caf"},"headline":"Migrate from Contentful to Sanity: A Complete Developer Guide","datePublished":"2025-10-24T11:16:35+00:00","dateModified":"2026-01-29T15:13:17+00:00","mainEntityOfPage":{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/"},"wordCount":2811,"commentCount":0,"publisher":{"@id":"https:\/\/pagepro.co\/blog\/#organization"},"keywords":["for CTO","webdev"],"articleSection":["For CTO","Sanity"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#respond"]}]},{"@type":["WebPage","FAQPage"],"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/","url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/","name":"How to Migrate Contentful to Sanity in 2025","isPartOf":{"@id":"https:\/\/pagepro.co\/blog\/#website"},"datePublished":"2025-10-24T11:16:35+00:00","dateModified":"2026-01-29T15:13:17+00:00","description":"Migrate Contentful to Sanity without losing data or SEO. Follow our 2025 guide with CLI commands, code examples, and schema setup tips.","breadcrumb":{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#breadcrumb"},"mainEntity":[{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763123991612"},{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124007738"},{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124023755"},{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124044595"},{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124054958"},{"@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124062087"}],"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/pagepro.co\/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https:\/\/pagepro.co\/blog\/"},{"@type":"ListItem","position":3,"name":"Migrate from Contentful to Sanity: A Complete Developer Guide"}]},{"@type":"WebSite","@id":"https:\/\/pagepro.co\/blog\/#website","url":"https:\/\/pagepro.co\/blog\/","name":"Pagepro","description":"Frictionless Next.js, Expo &amp; Sanity Development Blog","publisher":{"@id":"https:\/\/pagepro.co\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/pagepro.co\/blog\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/pagepro.co\/blog\/#organization","name":"Pagepro","url":"https:\/\/pagepro.co\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/pagepro.co\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2020\/08\/logo_pagepro-b66d228a1e-1.png","contentUrl":"https:\/\/pagepro.co\/blog\/wp-content\/uploads\/2020\/08\/logo_pagepro-b66d228a1e-1.png","width":440,"height":200,"caption":"Pagepro"},"image":{"@id":"https:\/\/pagepro.co\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/thisispagepro"]},{"@type":"Person","@id":"https:\/\/pagepro.co\/blog\/#\/schema\/person\/66e00cf32ef7d2d1b010523eff380caf","name":"Jakub Dakowicz","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/pagepro.co\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5e0855c6f563f4e1a4a53206089ce0cc?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5e0855c6f563f4e1a4a53206089ce0cc?s=96&d=mm&r=g","caption":"Jakub Dakowicz"},"description":"Jakub is the Chief Technology Officer at Pagepro, where he leads technical strategy and oversees the architecture of complex web platforms built with Next.js and headless CMS solutions. With nearly nine years at Pagepro and over five years leading the engineering team, he has been instrumental in shaping the company\u2019s architectural standards, development workflows, and scalability practices. Jakub focuses on building robust, composable systems that balance performance, maintainability, and long-term business flexibility. He drives technical decision-making across projects, ensuring that solutions are not only modern, but strategically aligned with client growth.","sameAs":["https:\/\/www.linkedin.com\/in\/jakub-dakowicz-939838102\/"],"url":"https:\/\/pagepro.co\/blog\/author\/jakub_dakowicz\/"},{"@type":"Question","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763123991612","position":1,"url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763123991612","name":"Why Do Companies Migrate Contentful to Sanity?","answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"Teams move from Contentful to Sanity for greater flexibility, customization, and predictable costs. Contentful\u2019s model isn\u2019t easily customizable either.\u00a0<br\/>Sanity, on the other hand, lets developers define their own content models, add custom input components, and collaborate in real time. It works better for growing teams that need control without the limits of a closed ecosystem.","inLanguage":"en-US"},"inLanguage":"en-US"},{"@type":"Question","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124007738","position":2,"url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124007738","name":"Why is Contentful So Expensive?","answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"The biggest issue with Contentful\u2019s pricing is the steep jump between the free and paid tiers. The free plan is generous for individual developers, but for teams only a few content types and users are allowed. The next available plan costs roughly $300\/month, which comes with additional purchases like different types of spaces.\u00a0","inLanguage":"en-US"},"inLanguage":"en-US"},{"@type":"Question","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124023755","position":3,"url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124023755","name":"Is Sanity CMS Free?","answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"Yes. Sanity offers a free forever plan suitable for small projects, MVPs, and personal sites. Paid plans start at around $15 per user\/month, adding collaboration features, increased API limits, and additional datasets.","inLanguage":"en-US"},"inLanguage":"en-US"},{"@type":"Question","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124044595","position":4,"url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124044595","name":"How Long Does it Take to Migrate Contentful to Sanity?","answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"Migration time depends on your setup. Using the official CLI tool, a simple project can be moved in a day or two. For larger projects with custom content models, the process can take a few weeks.","inLanguage":"en-US"},"inLanguage":"en-US"},{"@type":"Question","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124054958","position":5,"url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124054958","name":"What\u2019s the Difference Between Using the CLI Tool and Writing a Custom Script for Migration?","answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"The CLI tool is fast and automated. It exports data from Contentful, converts it into Sanity\u2019s format, and even generates schema files. Writing a custom migration script takes longer but gives you total control. You can redesign schemas, fine-tune data mapping, and decide exactly how relationships and fields are structured.","inLanguage":"en-US"},"inLanguage":"en-US"},{"@type":"Question","@id":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124062087","position":6,"url":"https:\/\/pagepro.co\/blog\/migrate-contentful-to-sanity\/#faq-question-1763124062087","name":"Can I Migrate Assets, Such as Images and PDFs, from Contentful to Sanity?","answerCount":1,"acceptedAnswer":{"@type":"Answer","text":"Yes. The official CLI handles assets automatically, while a custom script requires you to include them manually using the assets array and Sanity\u2019s upload API.","inLanguage":"en-US"},"inLanguage":"en-US"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/posts\/19860"}],"collection":[{"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/comments?post=19860"}],"version-history":[{"count":23,"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/posts\/19860\/revisions"}],"predecessor-version":[{"id":22896,"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/posts\/19860\/revisions\/22896"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/media\/19900"}],"wp:attachment":[{"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/media?parent=19860"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/categories?post=19860"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pagepro.co\/blog\/wp-json\/wp\/v2\/tags?post=19860"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}