Migrate to Prismic with AI
Migrate from WordPress to Prismic
WordPress sites mix three kinds of content: structured data (posts, custom post types, ACF fields), page layout (Gutenberg blocks or a page builder) and plugin output baked into post HTML. This guide moves the first cleanly, turns the second into a Prismic slice library, and isolates the third so it doesn't leak into your new site.
An AI agent does the heavy lifting at every step: it audits the site through the REST API, proposes the content model, generates slices and custom types, writes the migration script against the Prismic Migration API, and checks the result through the Prismic MCP. You stay in charge of every decision.
A migration is the best moment to rethink the site, not copy it. Port the content faithfully, but rebuild layouts as slices your client's marketers can reuse, rather than recreating every block and widget one to one.
Before you start
You need read access to the WordPress site and a fresh Prismic project to write into. Run everything on a new branch.
- The WordPress REST API reachable at /wp-json, plus an Application Password if you need drafts, private content or raw block markup
- ACF field groups set to "Show in REST API", if the site uses ACF
- An empty Prismic repository and a write API token from Settings > API & Security
- A Next.js project with the Prismic CLI, and Node 20 or later
- Claude Code (or Cursor) with the Prismic MCP connected to the new repository
1. Audit the WordPress site
Start by sizing the job. The REST API returns totals in its response headers, so you can count each content type without downloading anything.
curl -sI "https://example.com/wp-json/wp/v2/posts?per_page=1" | grep -i x-wp-total
curl -s "https://example.com/wp-json/wp/v2/types" | jq 'keys'Then ask your agent for a full inventory. Keep it read-only: the audit is the document you will share with the client to agree on scope.
Using the WordPress REST API at https://example.com/wp-json, inventory this site. Count posts, pages, custom post types, categories, tags, authors and media. List every Gutenberg block, ACF field group and shortcode in use, with how often each appears. Flag plugins whose output lives inside post content (forms, sliders, page builders). Write the result to migration/audit.md and do not change anything.
2. Map WordPress to Prismic
Most of the WordPress model has a direct Prismic equivalent. The decisions worth discussing with the client are which blocks become slices and how taxonomies are handled.
- Post type (post, page, custom post type) → repeatable custom type
- Gutenberg block or ACF Flexible Content layout → shared slice, with variations for near-identical layouts
- ACF repeater → repeatable group inside a slice or custom type
- Classic editor content → a rich text slice
- Categories and authors → content relationships to a category or author custom type; tags → Prismic tags
- Featured image → image field; Yoast or Rank Math meta → SEO & Metadata tab
- Menus and site options → single (non-repeatable) custom types such as navigation and settings
- Slug and permalink structure → UID plus routes in your link resolver
Read migration/audit.md and propose a Prismic content model: custom types, shared slices and their variations, and which Prismic field each WordPress field maps to. Prefer variations over near-duplicate slices, keep pages as a flat list of slices, and list any block you propose to drop. Write a mapping table to migration/mapping.md and wait for my approval before creating any model files.
Page builders such as Elementor, Divi or WPBakery store layout as shortcodes or serialized data that doesn't survive a migration. Treat those pages as a redesign: scrape the rendered page into slices with the "Scrape a site into Prismic" guide instead of parsing the stored markup.
3. Create the models
Once the mapping is approved, let the agent generate the slices, custom types and the components that render them, then push the models to the repository.
Create every custom type and shared slice in migration/mapping.md in this project, with a component for each slice styled with our existing design tokens and mock content for the slice simulator. Show me the list of files before pushing the models.
npx prismic push4. Write the migration script
The Migration API in @prismicio/client does the hard parts for you: it uploads each image once, resolves links between documents created in the same run, throttles requests, and puts everything in a Migration release so nothing goes live until you publish. The script below shows the shape for blog posts; your agent extends it to every type in the mapping.
npm install --save-dev @prismicio/client @prismicio/migrate tsximport * as prismic from "@prismicio/client";
import { htmlAsRichText } from "@prismicio/migrate";
const WP = "https://example.com/wp-json/wp/v2";
const writeClient = prismic.createWriteClient("your-repo", {
writeToken: process.env.PRISMIC_WRITE_TOKEN!,
});
const migration = prismic.createMigration();
// Page through any WordPress collection
async function fetchAll(path: string) {
const items = [];
for (let page = 1; ; page++) {
const res = await fetch(`${WP}/${path}?per_page=100&page=${page}&_embed`);
if (!res.ok) break;
items.push(...(await res.json()));
if (page >= Number(res.headers.get("x-wp-totalpages"))) break;
}
return items;
}
for (const post of await fetchAll("posts")) {
const media = post._embedded?.["wp:featuredmedia"]?.[0];
migration.createDocument(
{
type: "blog_post",
uid: post.slug,
lang: "en-us",
tags: [],
data: {
title: post.title.rendered,
publication_date: post.date.slice(0, 10),
featured_image: media
? migration.createAsset(media.source_url, media.slug, {
alt: media.alt_text,
})
: undefined,
meta_description: post.yoast_head_json?.description,
slices: [
{
slice_type: "rich_text",
variation: "default",
primary: { content: htmlAsRichText(post.content.rendered).result },
items: [],
},
],
},
},
post.title.rendered,
);
}
await writeClient.migrate(migration, {
reporter: (event) => console.log(event.type),
});Field and slice names above are placeholders; they must match your models. Ask the agent to write the full script from the mapping, block by block.
Using migration/mapping.md and the models in this project, write migration/wordpress.ts with the Prismic Migration API from @prismicio/client. Page through every post type, convert each Gutenberg block to its mapped slice, and fall back to a rich text slice for unmapped blocks while logging them to migration/report.md. Upload featured and inline images with createAsset, link categories and authors as content relationships, decode HTML entities in titles, and strip leftover shortcodes. Add --type and --limit flags so I can test on a few documents first.
5. Dry run, then run it all
Migrate ten documents of each type first and open them in the Page Builder from the Migration release. Fix the mapping, delete the test documents, then run the full migration.
PRISMIC_WRITE_TOKEN=xxx npx tsx migration/wordpress.ts --type post --limit 10
PRISMIC_WRITE_TOKEN=xxx npx tsx migration/wordpress.ts6. Check the result with the Prismic MCP
Before anyone publishes, let the agent compare what landed in Prismic with the audit. This catches silent failures that a spot check misses.
Using the Prismic MCP, list every document in the Migration release and compare the count per custom type with migration/audit.md. List documents with an empty slice zone, a missing featured image or meta description, rich text still containing shortcodes like [gallery], or images still pointing to the old WordPress domain. Add the findings to migration/report.md.
7. Redirects and go-live
Keep URLs identical where you can. Where the structure changes, generate redirects from the WordPress permalinks so search rankings follow the content.
Fetch every post, page and category permalink from the WordPress REST API and compare it with the URL our link resolver produces for the matching Prismic document. Generate permanent redirects in next.config for every URL that changes, including category and paginated archive URLs, and list any WordPress URL with no destination.
Freeze content in WordPress on cut-over day, re-run the script for anything modified since the first run (the REST API accepts a modified_after filter), publish the Migration release and switch DNS.
Common issues
- Shortcodes rendered as plain text: strip or convert them before calling htmlAsRichText
- HTML entities such as ’ in titles: decode them before writing text fields
- Inline images hot-linked to the old domain: re-upload them as Prismic assets
- ACF fields missing from the API: enable "Show in REST API" on each field group
- Drafts and private posts missing: authenticate with an Application Password and add status=any