All posts
Platform migrations

Sanity to Payload Migration: Moving to a Self-Hosted CMS

A Sanity to Payload migration guide: when self-hosting pays off, how to convert Portable Text to Lexical, and when staying on Sanity is the smarter call.

By Mehdi Hassan9 min read

The short answer

A Sanity to Payload migration makes sense when you want your content in your own database, your CMS inside your Next.js app and no hosted-platform dependency. The hard part is converting Portable Text to Lexical rich text. Stay on Sanity if real-time collaboration, its hosted CDN and zero infrastructure work matter more than ownership.

Key takeaways

  • Move to Payload for ownership: content lives in your Postgres or MongoDB, next to your Next.js code.
  • Stay on Sanity if you value hosted infrastructure, real-time co-editing and a mature visual editing setup.
  • Portable Text to Lexical is a JSON-to-JSON transform; custom blocks are where the effort goes.
  • Images need re-hosting and crop data mapped, or your hero images will shift after launch.
  • Rewrite every GROQ query as a Payload query before cutover, not after.

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

Sanity and Payload are both good headless CMSs, and moving between them is a real project, not a weekend export. This guide covers the decision first, then the mechanics: how Portable Text maps to Payload's Lexical editor, what breaks, and the order we'd do the work in. If you're still comparing every option, start with our headless CMS migration guide, which covers moves between Contentful, Sanity, Payload and Strapi.

Should you migrate from Sanity to Payload?

You should migrate from Sanity to Payload if owning your content and infrastructure is worth a few weeks of engineering, and you already run a Next.js site. You shouldn't migrate if your current Sanity setup works, your editors are happy and the only motive is a vague preference for open source.

The honest test is to write down the problem Sanity is causing. Good answers sound like this: our usage-based bill is hard to predict, legal wants content in our own database, we need the CMS and the front end in one repo and one deploy, or we want to query content with plain TypeScript instead of GROQ. A migration only pays back if it removes a specific, recurring cost.

For a side-by-side view of the two platforms beyond migration, our Sanity vs Payload comparison for Next.js marketing sites goes deeper on editing, hosting and developer experience.

Why do teams look for a self-hosted Sanity alternative?

Teams look for a self-hosted Sanity alternative mainly for data ownership, predictable costs and a simpler stack. Payload is open source, written in TypeScript, and since version 3 it installs directly into a Next.js app, so the admin panel and your marketing site share one codebase and one deployment.

  • Your database, your data. Payload stores content in Postgres, MongoDB or SQLite that you control. Sanity stores content in its hosted Content Lake; you can export it, but you don't run it.
  • One repo. With Payload, schema, admin and front end live together, and content queries run through Payload's Local API with no network hop.
  • Cost shape. Sanity's plans are priced around seats and usage. Payload itself is free; you pay for hosting and a database. Check Sanity's current plan page before you do any break-even math.
  • TypeScript all the way. Payload generates types from your collections, which many teams find easier than maintaining GROQ projections.

None of these is a reason on its own. Together, for a team that already deploys Next.js and has a developer on hand, they often are. We run Payload in production ourselves and wrote up what we learned in setting up Payload CMS for a marketing site.

When is Sanity's hosted model still the better call?

Sanity's hosted model is still the better call when you have no one to own infrastructure, when several editors work on the same documents at once, or when your editors rely on its visual editing and preview workflow. Self-hosting moves responsibility onto you: backups, database upgrades, uptime and security patches.

Pros

  • Staying on Sanity: no servers, databases or backups to manage
  • Real-time collaborative editing with presence, built in
  • Image CDN with on-the-fly crops and hotspots out of the box
  • Mature visual editing through the Presentation tool
  • Your current schema, queries and editor habits keep working

Cons

  • Content lives on Sanity's platform, not in your own database
  • Usage and seat-based pricing can be harder to forecast
  • GROQ is another query language for your team to maintain
  • Studio and front end are usually separate deployments

How does Portable Text convert to Lexical in Payload?

Portable Text converts to Lexical through a script that walks each Portable Text block and writes the matching Lexical node. Both formats are structured JSON, so nothing is guessed from HTML, but they're shaped differently: Portable Text is a flat array of blocks with marks, while Lexical is a nested tree under a root node.

Portable Text

Lexical in Payload

Watch out for

block with style normal

paragraph node

Empty blocks used as spacers

style h2, h3, h4

heading node with tag

Mismatched heading levels

style blockquote

quote node

Quotes with attribution fields

listItem bullet or number, level

list and listitem nodes, nested

Flat list items must be regrouped into lists

decorators strong, em, code

text node format flags

Custom decorators like highlight

markDefs link annotation

link node wrapping text

Internal links stored as references

image object

upload node pointing to a media doc

Hotspot and crop data

custom object (callout, code, CTA)

block node via the Blocks feature

Each needs a Payload block config

There are two routes. The direct route maps JSON to JSON, as above, and keeps everything. The shortcut renders Portable Text to HTML, then uses the HTML-to-Lexical converter that ships with Payload's Lexical package. The shortcut is faster to write, but custom blocks and internal reference links tend to flatten into plain HTML, so we only use it for simple blog bodies.

Hypothetical example: converting one paragraph with a link

For example, suppose a Sanity block has two children: a plain span reading Read the and a span reading pricing guide carrying a mark key that points to a link annotation in markDefs.

The script creates a Lexical paragraph node. The first span becomes a text node with format 0. The second span becomes a link node whose url comes from the annotation, with a text node inside it. If the annotation is an internal reference rather than a URL, the script looks up the referenced document's new Payload ID and writes an internal link instead. Run this on a sample of real documents first: edge cases like nested marks only show up in real content.

How do you migrate Sanity to Payload step by step?

You migrate Sanity to Payload by modeling the new collections first, exporting the dataset, transforming documents and assets with a script, importing through Payload's Local API, then rewriting queries and redirects before cutover. Do it in that order and keep Sanity live until the last step.

  1. Audit and remodel the schema

    List every Sanity document type and decide what becomes a collection, a global or a block. This is the moment to drop unused fields, as our guide to content modeling for a CMS migration explains.

  2. Export the dataset

    Use the Sanity CLI dataset export, which produces an NDJSON file of documents plus your assets. Skip draft documents unless editors need them.

  3. Move assets first

    Upload every image and file into a Payload upload collection, and store a map of old asset IDs to new media IDs. Carry alt text and crop or hotspot data across.

  4. Transform and import documents

    Run the Portable Text to Lexical script, swap references for new Payload IDs, and create documents through the Local API in dependency order: authors and categories before posts.

  5. Rewrite the front end queries

    Replace each GROQ query with a Payload query and check that every page renders the same fields.

  6. Check URLs and SEO

    Keep slugs identical where you can, add redirects where you can't, and compare titles, descriptions and canonical tags page by page.

  7. Freeze, re-run, cut over

    Freeze edits in Sanity, re-run the import for a final delta, switch the site to Payload and keep the Sanity project read-only for a while.

What breaks in a migration from Sanity to Payload?

The things that break are rarely the text. They're images, references, preview and the queries that tie it all together. Plan for each before you start.

  • References. Sanity references point to document IDs, and arrays carry their own keys. Every reference needs remapping to a new Payload ID.
  • Drafts and versions. Sanity drafts are separate documents with a drafts prefix. Decide whether to import them as Payload drafts or discard them.
  • Live preview. Sanity's Presentation tool doesn't come with you. Payload has its own live preview, but it has to be wired to your pages.
  • Asset URLs. Any image URL hard-coded to Sanity's CDN in content or code will break once you close the project.

If an AI coding agent is doing the bulk transform work, these are exactly the points a human still has to check. Our piece on AI-assisted website migration covers where agents help and where they don't.

Payload vs Sanity: which should you choose after weighing the move?

Choose Payload if you want a CMS that lives in your codebase and database; choose to stay on Sanity if you want a hosted platform that someone else runs. The table below sums up the trade-offs we see most often.

Factor

Sanity

Payload

Hosting

Hosted by Sanity

You host it, often with the Next.js app

Where content lives

Sanity Content Lake

Your Postgres, MongoDB or SQLite

Rich text

Portable Text

Lexical

Querying

GROQ or GraphQL

Local API, REST or GraphQL

Collaborative editing

Real-time, built in

Document locking, not live co-editing

Ops work

Minimal

Backups, upgrades, monitoring

Frequently asked questions

Is Payload CMS really free?
Payload's code is open source and free to use, including commercially. You still pay for what it runs on: hosting for the Next.js app, a database such as Postgres or MongoDB, and storage for images and files. For a typical marketing site those costs are modest, but they're yours to manage, along with backups and upgrades.
Can I keep my Sanity Studio while moving the front end?
Yes. Many teams rebuild the front end first while still reading from Sanity, then move the content to Payload as a separate step. This splits one big risk into two smaller ones. It also lets you test the new site on real content before the CMS changes, which makes problems easier to trace.
How long does a Sanity to Payload migration take?
It depends on the number of document types, how many custom Portable Text blocks you use and how much content you have. A simple blog with a handful of types is far quicker than a site with page builders and many references. Write the transform for your most complex document type first; that tells you more about timing than any estimate.
Will my SEO drop after switching from Sanity to Payload?
Not if the URLs, titles, meta descriptions, canonical tags and structured data stay the same. Changing the CMS is invisible to Google when the rendered pages match. Rankings drop when slugs change without redirects, when metadata fields are lost in the import, or when pages render differently. Compare a crawl of the old and new site before launch.
Do I need to convert Portable Text to Lexical, or can I store it as is?
You could store Portable Text in a Payload JSON field and keep rendering it with your current components. That avoids the conversion, but editors lose a proper rich-text editor in Payload's admin. It can work as a temporary step for archived content, but for anything editors still touch, converting to Lexical is worth it.

· CEO & Design Director

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

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

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

Schedule Call