Migrating to Sanity from WordPress, Contentful, or Drupal means auditing your content model, mapping fields honestly, and choosing the right extraction tools for your source CMS. This playbook covers realistic timelines, budget ranges, and the technical steps for each migration path. It also covers the content-modeling decisions that keep a project on schedule.

Key takeaways

  • Audit your content model before writing migration code. Undocumented custom fields and taxonomies cause most delays.
  • WordPress migrations typically run 4-8 weeks, Contentful-to-Sanity 3-5 weeks, and Drupal migrations 5-16+ weeks depending on complexity.
  • Sanity's Growth plan ($99/month) is usually cheaper than Contentful's Team plan ($300/month) at similar usage. See Sanity pricing and Contentful pricing.
  • Frontend development, not content migration, is usually the bigger cost in a headless CMS switch.
  • Redirect mapping and SEO metadata migration matter a lot. Teams often underestimate them.

Updated 15 August 2026: sources added, experience claims checked against our project record, summary added.

Sanity Migration Playbook: Moving from WordPress, Contentful, or Drupal

Why Teams Are Moving to Sanity in 2026

Let's get the obvious stuff out of the way. Sanity's real-time collaborative editing, customizable Studio, and structured content approach are genuinely good. But teams rarely start a migration just because they read about Sanity's features. Something usually broke first.

WordPress sites can hit scaling walls when content volume grows and custom post types get complex. Contentful's pricing starts to squeeze at the enterprise tier. Premium plans can run several thousand dollars a month for what's basically a content API (Contentful pricing). WordPress still powers a large share of the web (w3techs.com), so it's one of the most common source platforms in migration projects. Drupal teams increasingly struggle to find developers who want to work with PHP templating.

Sanity's pricing is usually more predictable for most teams. The free tier covers up to 100K API requests a month and 500K assets. The Growth plan at $99/month/project gets you 2.5M API requests and 1M assets. For comparison, Contentful's Team plan runs $300/month, while Contentful's Premium tier is quote-based and can run well into four figures monthly.

If your current CMS works fine and your team is productive, don't migrate just because Sanity is newer. Migrations always cost more than you expect.

Pre-Migration Audit: The Step Everyone Skips

Before you write a single line of migration code, run a content audit. Not a quick scan, an actual audit. Here's what that looks like:

Content Inventory

Document every content type, every field, and every relationship. A useful audit spreadsheet includes these columns:

  • Content type name
  • Total items
  • Fields (with types)
  • Relationships to other content types
  • Media attachments (count and total size)
  • Custom functionality (shortcodes, widgets, embeds)
  • Last modified date
  • Still relevant? (Yes/No/Maybe)

You'll often be surprised how much content is dead weight. In practice, a large share of older content turns out to be irrelevant after an audit. Cutting it reduces the volume you need to migrate, test, and validate.

Technical Dependency Mapping

List every plugin, module, or integration your current CMS uses. For each one, ask:

  1. Can Sanity handle this natively?
  2. Is there a Sanity plugin for it?
  3. Do we need to build a custom solution?
  4. Can we drop this entirely?

This mapping alone can save weeks of surprises down the road.

Team Readiness Assessment

Sanity Studio is React-based. Content editors need training, and developers need to learn GROQ (or use GraphQL, though GROQ is where Sanity really shines). Budget 1-2 weeks for team onboarding. Treat it as a line item, not a nice-to-have.

WordPress to Sanity Migration

WordPress is one of the most common source CMS platforms in migration work. It's also one of the trickiest, because WordPress isn't just a CMS. It's an application platform that teams bolt everything onto over time.

One example of this exact path: bdManagedIT, a Central-Georgia MSP, moved from WordPress to Astro plus Sanity on Netlify and landed on 95+ PageSpeed scores with zero-JS static pages. Read the case study.

What Transfers Cleanly

  • Posts and pages (basic content)
  • Categories and tags
  • Featured images
  • Author data
  • Basic custom fields (ACF, Meta Box)

What Gets Messy

  • Gutenberg blocks: Each block type needs a matching Sanity Portable Text custom block or object type. Sites with many custom Gutenberg blocks need significant time here.
  • Shortcodes: These need parsing and converting to Portable Text annotations or custom blocks. WPBakery and Elementor shortcodes are especially painful.
  • Plugin-generated content: WooCommerce products, Yoast SEO data, and ACF repeater fields each need custom migration logic.
  • Media library: WordPress stores multiple image sizes. Sanity handles image transformations on the fly, so you only need the originals. But finding them in a messy wp-uploads folder takes patience.

Migration Script Approach

A common approach uses a Node.js script that hits the WordPress REST API and writes to Sanity's mutation API:

import { createClient } from '@sanity/client'
import fetch from 'node-fetch'

const sanity = createClient({
  projectId: 'your-project-id',
  dataset: 'production',
  token: process.env.SANITY_WRITE_TOKEN,
  apiVersion: '2025-01-01',
  useCdn: false,
})

const WP_API = 'https://yoursite.com/wp-json/wp/v2'

async function migratePosts(page = 1) {
  const res = await fetch(`${WP_API}/posts?per_page=100&page=${page}`)
  const posts = await res.json()
  const totalPages = res.headers.get('x-wp-totalpages')

  const transaction = sanity.transaction()

  for (const post of posts) {
    transaction.createOrReplace({
      _id: `wp-post-${post.id}`,
      _type: 'post',
      title: post.title.rendered,
      slug: { current: post.slug },
      publishedAt: post.date,
      // Body requires HTML-to-Portable-Text conversion
      body: await convertToPortableText(post.content.rendered),
    })
  }

  await transaction.commit()
  console.log(`Migrated page ${page} of ${totalPages}`)

  if (page < totalPages) {
    await migratePosts(page + 1)
  }
}

The convertToPortableText function is where most of the migration complexity lives. The @sanity/block-tools package combined with jsdom for HTML parsing handles basic HTML well, but custom elements and shortcodes need individual handlers.

Realistic Timeline

For a typical WordPress site with 500-2,000 posts, standard custom fields, and a handful of custom post types: 4-8 weeks including content modeling, migration scripting, validation, and editor training.

Sanity Migration Playbook: Moving from WordPress, Contentful, or Drupal - architecture

Contentful to Sanity Migration

Contentful-to-Sanity is the smoothest migration path of the three. Both are structured content platforms with similar mental models, and your content is already in a headless CMS with defined content types and fields.

Key Differences to Account For

Feature Contentful Sanity
Rich text Rich Text (JSON-based) Portable Text (JSON-based)
Content modeling Web UI Code-defined schemas
Query language GraphQL / REST GROQ (+ GraphQL)
Localization Built-in field-level Plugin or custom
References Links (Entry/Asset) References with types
Webhooks Yes Yes
Asset handling Built-in CDN Sanity CDN + hotspot/crop
Pricing (mid-tier) ~$300/mo (Team) $99/mo (Growth)

Rich Text Conversion

Contentful's Rich Text and Sanity's Portable Text are both JSON-based, which helps, but the structures differ. You'll need to write a transformer:

function contentfulRichTextToPortableText(richTextField) {
  return richTextField.content.map(node => {
    switch (node.nodeType) {
      case 'paragraph':
        return {
          _type: 'block',
          style: 'normal',
          children: node.content.map(mapInlineContent),
        }
      case 'heading-2':
        return {
          _type: 'block',
          style: 'h2',
          children: node.content.map(mapInlineContent),
        }
      case 'embedded-entry-block':
        // Map to your custom Portable Text type
        return mapEmbeddedEntry(node)
      // ... handle all node types
    }
  }).filter(Boolean)
}

Content Type to Schema Mapping

Contentful content types map fairly directly to Sanity document and object types. The biggest shift is that Sanity schemas are defined in code (JavaScript/TypeScript), not in a web UI. This is an advantage, since your content model lives in version control.

Use the Contentful Management API to export your content model, then write a script that generates Sanity schema files:

contentful space export --space-id YOUR_SPACE_ID --export-dir ./export

Realistic Timeline

For a Contentful space with 10-20 content types and 5,000-10,000 entries: 3-5 weeks. It's faster because you're already thinking in structured content.

Drupal to Sanity Migration

Drupal migrations tend to be the most demanding of the three. Not because Drupal is bad, it's a powerful system, but Drupal sites tend to be old, heavily customized, and running on infrastructure that's often poorly documented.

The Drupal-Specific Challenges

  • Content types with dozens of fields: Drupal makes it easy to add fields, and mature builds often accumulate many that go unused.
  • Taxonomy term references: Drupal's taxonomy system is flexible but can create deeply nested hierarchies that need flattening for Sanity.
  • Paragraphs module: If the site uses Drupal Paragraphs (most modern Drupal sites do), each paragraph type becomes a Portable Text block type or Sanity object. This is usually the biggest single task.
  • Media entities: Drupal 9/10's media system is more complex than WordPress's. Multiple media types, reusable media entities, and file field configurations all need mapping.
  • Multilingual content: Drupal's translation system is sophisticated. Sanity doesn't match it at the same level out of the box. You'll need the @sanity/document-internationalization plugin or a field-level approach.

Migration Approach

Drupal's JSON:API module, included in Drupal core since 9.x (drupal.org), is a common extraction layer:

async function fetchDrupalContent(type, page = 0) {
  const limit = 50
  const offset = page * limit
  const url = `${DRUPAL_URL}/jsonapi/node/${type}?page[limit]=${limit}&page[offset]=${offset}&include=field_image,field_paragraphs`

  const res = await fetch(url, {
    headers: { Authorization: `Basic ${DRUPAL_AUTH}` },
  })
  return res.json()
}

For older Drupal 7 sites without JSON:API, you may need to query the database directly. Drupal 7's database schema is an experience of its own. The field_data_* tables will haunt your dreams.

Realistic Timeline

Drupal migrations vary a lot. A straightforward Drupal 10 site with 5-10 content types: 5-8 weeks. A legacy Drupal 7 site with 30+ content types, Paragraphs, and multilingual content: 8-16 weeks.

Content Modeling: Getting Your Schemas Right

Here's the thing most migration guides won't tell you: don't replicate your old content model in Sanity. This is your chance to fix years of accumulated content debt.

Common Modeling Mistakes

  1. Creating a 1:1 field mapping: Just because WordPress had a "subtitle" custom field doesn't mean Sanity needs one. It might belong inside a structured "hero" object instead.
  2. Over-nesting objects: Sanity lets you nest objects deeply. Resist the urge. Flat-ish schemas are easier to query with GROQ and easier for editors to work with.
  3. Ignoring Portable Text's power: Don't just dump HTML into a single text field. Design custom block types that match your content patterns, a callout block, a code-snippet block, an image-with-caption block, to make editors' lives easier.

Schema Design Process

A reliable process follows this order:

  1. Audit existing content (done in pre-migration)
  2. Identify the actual content patterns, not what the old CMS imposed
  3. Design schemas on paper or a whiteboard first
  4. Build schemas in code
  5. Import a small test batch (50-100 items)
  6. Have editors test the Studio experience
  7. Iterate on schemas before full migration

Steps 5-7 are critical and often skipped. We've written more about content modeling approaches in our headless CMS development work.

Data Migration Strategies and Tooling

Essential Tools

  • @sanity/client: The official JavaScript client for reading/writing Sanity data
  • @sanity/block-tools: Converts HTML to Portable Text
  • sanity dataset import/export: CLI tools for full dataset operations
  • ndjson: Sanity uses newline-delimited JSON for imports. Get comfortable with it.
  • jsdom or htmlparser2: For HTML parsing during rich text conversion

Migration Architecture

Every migration works best as a pipeline with four stages:

Extract → Transform → Load → Validate

Each stage is a separate script. This matters because you'll run the migration multiple times before the final production run, and separate stages let you re-run just the parts that need fixing.

## Extract
node scripts/extract-wordpress.js > data/raw-posts.ndjson

## Transform
node scripts/transform-posts.js < data/raw-posts.ndjson > data/sanity-posts.ndjson

## Load
sanity dataset import data/sanity-posts.ndjson production --replace

## Validate
node scripts/validate-migration.js

Handling Assets

Images and files are always the slowest part. Sanity's asset pipeline is solid, but uploading thousands of images takes time. Tips:

  • Upload assets first, and keep a mapping of old URLs to new Sanity asset IDs
  • Use concurrent uploads, but respect the API rate limits for your plan
  • Verify image dimensions and formats before upload
  • Don't migrate thumbnail sizes. Sanity generates these on the fly through its image CDN

The Hidden Costs Nobody Talks About

Here are costs that don't show up in a typical migration estimate.

URL Redirects

If you're changing your frontend (likely, if you're moving to a headless CMS), you need a redirect mapping for every URL. For SEO, this is non-negotiable. A site with 5,000 pages needs 5,000 redirect rules. Tools like next.config.js redirects or Netlify's _redirects file can handle this, but someone has to build the mapping.

SEO Metadata Migration

Yoast SEO data from WordPress, Metatag module data from Drupal, and Contentful's SEO fields all need to come over. Custom meta titles, descriptions, Open Graph images, canonical URLs, and structured data make this a project within the project.

Editor Training and Documentation

Budget 2-4 days minimum. Sanity Studio is intuitive, but it's different from what editors know. Good onboarding usually includes custom Studio documentation with screenshots and a few short walkthrough videos.

Frontend Development

This is the elephant in the room. Migrating content to Sanity is only half the project. You also need a frontend that consumes the content. Whether you use Next.js, Astro, or another framework, the frontend build is often the larger part of the total project cost. Check out our work with Next.js and Astro if you're weighing frontend options.

Timeline and Budget Comparison

Typical timeframes and budgets based on scope and complexity:

Migration Path Content Volume Complexity Timeline Budget Range
WordPress → Sanity < 1,000 pages Low 3-5 weeks $8K-$15K
WordPress → Sanity 1,000-10,000 pages Medium 6-10 weeks $15K-$35K
WordPress → Sanity 10,000+ pages High 10-16 weeks $35K-$75K
Contentful → Sanity < 5,000 entries Low-Medium 3-5 weeks $7K-$18K
Contentful → Sanity 5,000-20,000 entries Medium 5-8 weeks $18K-$40K
Drupal → Sanity < 2,000 nodes Medium 5-8 weeks $12K-$25K
Drupal → Sanity 2,000-15,000 nodes High 8-14 weeks $25K-$60K
Drupal 7 → Sanity Any Very High 10-20 weeks $35K-$90K

Note: These ranges include content migration only. Frontend development is additional. Contact us at /pricing for project-specific estimates.

These figures include content modeling, migration scripting, data validation, and basic editor training. They don't include frontend development, design, or ongoing maintenance.

Post-Migration Checklist

A thorough post-migration checklist covers:

  • All content types migrated and verified
  • All references/relationships intact
  • All images and files uploaded and linked correctly
  • Rich text content renders correctly (check for broken formatting)
  • URL redirects in place and tested
  • SEO metadata migrated (titles, descriptions, OG data)
  • XML sitemap regenerated
  • Search console updated with new sitemap
  • Analytics tracking preserved
  • Editor accounts created and permissions set
  • Editor training completed
  • Content preview (draft mode) working
  • Webhooks configured for build triggers
  • Backup of source CMS data archived
  • DNS changes planned (if applicable)
  • Performance baseline measured
  • 404 monitoring set up for first 30 days

That last point matters. No matter how thorough your redirect mapping, some URLs slip through. Monitor 404s closely for the first month.

FAQ

How long does a typical WordPress to Sanity migration take?

A standard WordPress site with under 2,000 posts and straightforward custom fields takes 4-8 weeks to migrate to Sanity. That covers content modeling, migration scripting, data validation, and editor training. Content volume matters less than the complexity of your content types.

Sites with complex Gutenberg blocks, WooCommerce, or multilingual content can take 10-16 weeks instead.

Can I migrate from Contentful to Sanity without losing data?

Yes, and Contentful-to-Sanity is the cleanest migration path among the three covered here, because both platforms use structured, JSON-based content. The mapping between content types is fairly direct, rich text needs conversion to Portable Text, and you shouldn't lose data if you validate carefully before cutover.

Running the migration against a staging dataset first, then doing a thorough content comparison before cutover, catches most mapping issues.

What happens to my SEO rankings during a CMS migration?

If you handle redirects properly, keep your URL structure where possible, and migrate all SEO metadata, you should see minimal ranking impact. Google's documentation on site moves notes that properly redirected migrations may see a temporary dip of a few weeks before recovering.

The key word is properly: skip the redirect mapping and rankings often drop hard.

Is Sanity cheaper than Contentful for enterprise use?

In most cases, yes, sometimes by a lot. Sanity's Growth plan at $99/month covers usage that would require Contentful's $300/month Team plan, and the price gap widens further at enterprise scale, where Contentful's custom pricing can run several thousand dollars a month.

Contentful's Premium pricing isn't publicly listed but typically runs into the thousands per month. Sanity's enterprise pricing is also custom, but its usage limits tend to be more generous at comparable spend.

Should I migrate my Drupal 7 site to Sanity or upgrade to Drupal 10 first?

Go directly to Sanity rather than upgrading first. Migrating from Drupal 7 to Drupal 10 is nearly as much work as migrating to a different CMS entirely, so if you're already investing in a major migration, move straight to the platform you want long-term.

The architecture changed significantly between Drupal 7 and Drupal 10, so treat this as a full platform migration rather than an upgrade. The one exception: if your team is deeply invested in the Drupal ecosystem and just wants to modernize, Drupal 10 with a headless frontend is a valid path.

Do I need to rebuild my frontend when migrating to Sanity?

If you're coming from a monolithic CMS like WordPress or Drupal, yes. You'll need a new frontend since Sanity is headless, and this is usually the bigger part of the project. Coming from Contentful, you can often reuse your existing frontend with modified API calls.

That reuse works especially well if you're already using Next.js or a similar framework. We handle both the CMS migration and frontend builds as integrated projects.

Can I run my old CMS and Sanity in parallel during migration?

Yes, running both systems in parallel is a good idea and standard practice for most CMS migrations. Keep both live for 2-4 weeks after the initial migration so editors keep working in the old CMS while you validate data in Sanity, then freeze content before the final cutover.

Freeze content in the old system 48 hours before your final migration run so you're not chasing a moving target.

What's the biggest mistake teams make during a Sanity migration?

The biggest mistake is replicating the old CMS structure exactly inside Sanity, field for field. Teams coming from WordPress often build WordPress-shaped schemas: generic page types with flexible layouts, instead of purpose-built content types that match how the content and editors actually work.

Sanity's strength is structured content, so use the migration as a chance to model your content properly. Spending an extra week on content modeling typically saves weeks of rework later. If you want guidance on this, reach out to us.

Key takeaway:

Audit content before coding. Migrations always cost more than expected.