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.
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.
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.
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.
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.
Rewrite the front end queries
Replace each GROQ query with a Payload query and check that every page renders the same fields.
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.
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 |
