WordPress → Sanity
Move WordPress posts, pages, authors, categories, tags, and media into Sanity with a CLI migration script over the REST API. Convert HTML or Gutenberg blocks to Portable Text. Rebuild the frontend. Page builders, SEO plugins, and ecommerce are out of Sanity's published course unless they appear on the API.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
WordPress
- Unmatched ecosystem breadth and content ownership support almost any publishing model
- Editors get a purpose-built publishing workflow instead of raw files
- Plugin, hosting, security, and performance maintenance accumulate over time
- Themes, extensions, and content models add maintenance and portability constraints
Sanity
- Content Lake, schema-as-code, and customizable Studio support sophisticated structured content
- Editors get a purpose-built publishing workflow instead of raw files
- GROQ, schema code, and custom editorial tooling demand engineering ownership
- Themes, extensions, and content models add maintenance and portability constraints
Sanity: Content Lake, schema-as-code, and customizable Studio support sophisticated structured content. This removes a major source-side concern: Plugin, hosting, security, and performance maintenance accumulate over time.
What you lose: Unmatched ecosystem breadth and content ownership support almost any publishing model. What you inherit: GROQ, schema code, and custom editorial tooling demand engineering ownership.
Jump to a section
Know the shape of the move.
This timeline assumes
- Editorial planning scale: up to 2,500 posts and pages, 20 authors, and 10 GB of media that the REST API can list.
- Sanity's course uses /wp-json/wp/v2 and the CLI Migration tool. It does not convert Elementor, Divi, Yoast, or WooCommerce unless those values are on the API.
- You will build a new frontend. Sanity is not a WordPress theme host.
- WordPress stays authoritative until the Sanity dataset and the new site pass URL, SEO, and editor checks.
- We record evidence for every blocking verification check.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Posts, pages, users, categories, and tags | partial | critical | Sanity's course imports those types through the REST API, creates references, and uploads assets. Custom post types need extra script work. | Write schema types first. Dry-run the migration script. Reconcile counts by type. |
| HTML and Gutenberg blocks | partial | critical | The course converts Classic or Block editor HTML into Portable Text. Custom blocks need schema types you write. | Pick rendered HTML or raw context=edit, then convert a sample of every block type. |
| Page builders and plugin HTML | lost | critical | Sanity says the course does not handle Elementor, Divi, SEO, or ecommerce content except what the REST API exposes. | Inventory builders. Rebuild those pages as structured documents or keep them out of scope. |
| Media library | partial | critical | Attachments can upload into the Content Lake. Image styles, unused crops, and off-library files do not. | Upload from attachment records, keep filenames, and check a sample of inline images in Portable Text. |
| Themes, plugins, and PHP | lost | critical | None of that runs in Sanity. | Replace each runtime feature with Studio, a frontend, or an external service. |
| URLs and redirects | manual | critical | Sanity documents do not keep WordPress permalinks by themselves. Field reports keep finding plugin routes months later. | Crawl the live site, map every public URL, and ship redirects with the frontend. |
| SEO plugin data and JSON-LD | manual | high | Yoast and similar plugins inject titles, canonicals, and schema that vanish when the theme dies. | Read plugin fields from the API if they are exposed, then store SEO fields on the Sanity document and render them yourself. |
| Comments and revisions | manual | medium | The course says you can learn to import comments. Revisions are not a promised Studio history. | Decide whether comments move, stay on WordPress, or go to a new system. |
| Menus, widgets, and theme options | manual | high | Those live in theme_mod and options, not in a portable post. | Recreate navigation as Sanity documents or frontend config. |
| Users and application passwords | partial | high | Authors can import as documents. WordPress logins do not become Sanity logins. Raw block conversion needs an application password and context=edit. | Invite Studio users separately. Store the application password only for the import. |
| REST API access | manual | critical | Security plugins often block /wp-json/wp/v2. The course tells you to confirm the route first. | curl the posts endpoint and fix the block before you write the script. |
| Frontend and hosting | manual | critical | Sanity does not publish a public site by itself. | Ship the new frontend and cut DNS only after previews and redirects pass. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| post | Sanity document type you define | transform | Keep the WordPress id as a source field for reruns. |
| page | page document | transform | Map parent pages if you still need hierarchy. |
| category / tag | category and tag documents plus references | transform | Create terms before posts so references resolve. |
| author | author document or Sanity user reference | transform | Studio login is separate. |
| featured image / attachment | Sanity image asset | transform | Upload once and reuse the asset id. |
| post_content HTML | Portable Text | transform | Convert Classic or Gutenberg; custom blocks need schema. |
| permalink | slug plus a redirect | manual | Do not assume the frontend path matches. |
| Yoast or SEO plugin fields | SEO fields on the document | manual | Only if the API exposes them. |
| custom post type | extra document type | manual | The published course does not import these for you. |
Make the move recoverable.
Create the source-of-truth backup
Keep a WordPress export and a URL crawl before the script writes to Sanity.
- Run Tools → Export for posts, pages, and media as a legal archive.
- Crawl public URLs, titles, and status codes.
- Confirm /wp-json/wp/v2/posts responds.
- Inventory post types, plugins, builders, and SEO fields.
- Create the Sanity project, dataset, and schema types.
Proof to capture: A dated manifest lists export files, crawl counts, API access, and schema types.
CLI migration script
Follow Sanity's recommended bulk path with dry runs.
- Skip document types in the prompt and replace the template.
- Query the REST API and return createOrReplace mutations so reruns are safe.
- Dry-run, then run with --no-dry-run only after counts look right.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Portable Text conversion
Turn HTML or raw blocks into documents editors can use.
- Convert a sample of Classic, Gutenberg, embeds, and galleries.
- Store unknown blocks as signed exceptions, not silent drops.
- Preview each type in Studio.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
The REST API is blocked
/wp-json/wp/v2/posts does not return posts.
- Consequence
- The script imports nothing or a login page.
- Mitigation
- Fix the security plugin or use an application password before you write mutations.
Stop if: The posts endpoint is not readable.
Page-builder HTML becomes unusable Portable Text
A sample page is a soup of shortcodes or builder markup.
- Consequence
- Editors cannot edit the page in Studio.
- Mitigation
- Rebuild those templates. Do not call the import done.
Stop if: A critical template has no structured destination.
Redirects miss plugin routes
Search Console later shows URLs the crawl never saw.
- Consequence
- SEO drops on URLs nobody mapped.
- Mitigation
- Crawl, then watch Search Console after launch and add redirects.
Stop if: A mapped money URL 404s after DNS cutover.
Do the work in this order.
- Days 1–5 · inventory
Inventory and schema
10–18 hours active3–5 days elapsedEditor review waiting- Crawl URLs and list post types.
- Confirm the REST API.
- Write Sanity schema for the types you will import.
Depends on: WordPress and Sanity admin access
Stop / go checkpointExport?
Go when: API, schema, and URL map have owners.
Stop when: A critical builder page has no plan.
- Days 4–7 · backup
Export and crawl
4–8 hours active1–3 days elapsedExport download waiting- Download the WordPress export.
- Store the crawl.
- Create the dataset.
Depends on: Approved inventory
Stop / go checkpointScript?
Go when: Export and crawl counts match the site.
Stop when: The export is empty or the API is blocked.
- Days 7–20 · pilot
Script and frontend
30–70 hours active8–14 days elapsedEditor preview waiting- Dry-run the import.
- Convert Portable Text samples.
- Build routes, previews, and SEO fields.
Depends on: Verified backup
Stop / go checkpointScale?
Go when: Pilot documents, assets, and templates pass editor review.
Stop when: A critical block or URL is wrong.
- Days 20–28 · cutover
Full import and DNS
10–25 hours active2–6 days elapsedDNS and cache waiting- Run the production import.
- Ship redirects.
- Switch DNS after smoke checks.
Depends on: Passed pilot
Stop / go checkpointOpen production?
Go when: Counts, redirects, and the homepage match the crawl.
Stop when: A money URL 404s.
- Days 26–35 · observe
Observe and close
8–14 hours active6–10 days elapsedSearch Console waiting- Watch 404s and editor workflow.
- Keep WordPress read-only.
Depends on: Verified cutover
Stop / go checkpointClose rollback?
Go when: No trigger fires through one publishing cycle.
Stop when: Indexed URLs fail or editors cannot publish.
Cut over with a way back.
Cutover
Make Sanity plus the new frontend the only public site after a reconciled import.
- Freeze WordPress publishing.
- Run the production migration script and reconcile counts.
- Publish the frontend with the redirect map.
- Switch DNS after smoke checks on money URLs.
- Keep WordPress recoverable.
Proof to capture: The new host serves the site. Counts match. Mapped URLs resolve. Editors can publish in Studio.
Rollback
Return the public site to WordPress without losing reviewed Sanity-era edits.
- Stop publishing on the new frontend.
- Point DNS back at WordPress.
- Export Sanity documents created after cutover.
- Apply only reviewed content back to WordPress if needed.
- Run the same URL checks on WordPress.
Proof to capture: WordPress serves the public site again and the new frontend is not receiving production traffic.
- Money URL 404
- Import count mismatch
- Unusable builder content
- Editor cannot publish
- API was blocked mid-import
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
V-01Blocking | REST access | GET /wp-json/wp/v2/posts and confirm a post list. | JSON posts, not a block page. | curl headers. | |
V-02Blocking | Type counts | Compare WordPress counts to Sanity documents by type. | Every scoped type matches or has a signed exception. | Count sheet. | |
V-03Blocking | Portable Text samples | Open Classic, Gutenberg, embed, and gallery samples in Studio. | Editors can edit each approved type. | Studio screenshots. | |
V-04Blocking | Image durability | Open featured and inline images as ordinary Studio users. | Assets resolve without WordPress URLs. | Asset manifest. | |
V-05Blocking | Redirect map | Request every money URL from the crawl. | 200 or a single 301 to the signed destination. | Crawl diff. | |
V-06Blocking | Titles and canonicals | Compare rendered title, description, and canonical on key URLs. | Approved values, not empty plugin leftovers. | SEO sheet. | |
V-07Blocking | Editor path | Create, preview, and publish one document. | The frontend updates without WordPress. | Publish log. | |
V-08Blocking | WordPress export | Confirm the WXR export and host still boot. | You can point DNS back. | Rollback note. |
Remove the scaffolding safely.
Safe after: One publishing cycle, a Search Console pass, and at least seven stable days.
- Archive the WXR export, crawl, and migration logs.
- Revoke the WordPress application password.
- Keep WordPress read-only through the retention window.
- Remove leftover WordPress embeds only after redirects are stable.
- Document Studio roles and the frontend deploy.
- Schedule the next schema and redirect review.
When the plan met reality.
First-hand accounts are preferred. Vendor case studies are labeled, and every note below is an editorial paraphrase—follow the link for the full context.
OneOffObject left a long-running WordPress site in May 2026 for Sanity, Astro, and Cloudflare Pages. A Node script turned the WXR export into NDJSON: 167 posts, 166 main images, 499 body images, and 625 image URLs, with no unresolved images at the end. Affiliate embeds and tables stayed as htmlEmbed blocks. Article slugs stayed at /{slug}/. Email moved to Sakura first, then DNS switched on May 13. WordPress went offline on May 17.
- Convert what editors need as Portable Text, and keep awkward HTML as a bounded embed instead of forcing every table into blocks.
- Move mail DNS before the website cutover if the old host still owns MX records for the same domain.
- The work was a rebuild, not a theme swap. Static pages later left Sanity and became Astro files.
- Keeping post slugs and splitting email from web DNS mattered more than a perfect Portable Text conversion.
Verify against the primary material.
Platform behavior changes. Check these sources and the review dates above before executing a production migration.
- Sanity Learn: migrating content from WordPressAccessed 2026-10-29
- WordPress: Tools Export screenAccessed 2026-10-29
- WordPress: REST API handbookAccessed 2026-10-29