wp-migrate-core can now help work through its own repair queue
Published
wp-migrate-core can now help work through its own repair queue
AI-assisted WordPress migration to Astro, Next.js, Payload and Pruvious. Inspect your export, automate repair triage, review suggestions for alt text, shortcodes and URLs, and carry accepted decisions into a repeatable migration.

Moving a WordPress site means working through the things an export leaves unresolved: unsupported shortcodes, missing image descriptions, old URLs, page-builder content and features that need rebuilding.
wp-migrate-core turns your WordPress XML export into a migration plan, then helps you work through it with optional AI assistance. Connect your own compatible AI provider to prioritize findings, draft decisions and prepare the next conversion.
The AI assistance covers five tasks:
Suggestions come with local review pages. Accepted changes are saved as reusable decisions, so you can regenerate from the original export without repeating the same cleanup by hand.
Choose a static Astro or Next.js project, or a Payload or Pruvious CMS app with content administration. Supply a local uploads folder to copy media, or point the generated content at an existing media host. Content inventories, navigation, internal links, redirect rules and verification reports help you check what made it across.
The migration engine runs locally. AI assistance runs when requested and sends the permitted inputs to your chosen provider. Themes, forms, plugin behavior and the finished site design still need development and review.
Open source, available on npm, with a bundled demo you can explore before using your own export.
Published
wp-migrate-core can now help work through its own repair queue
Published
Leaving WordPress should still leave you somewhere to edit your content, that was the reason for adding Payload CMS as a destination alongside Astro. The updates from 0.8.0 to 0.9.1 also bring across more of the original site structure and add checks for what actually reached the destination. 0.9.1 is now published on npm.
privacy.send: "content", refuses pages that would be redacted or truncated, and checks for excessive text loss when applying hand-edited patches.External download
The repair queue gets an optional, bring-your-own-key AI path — and the contract turns stable. The migration engine is unchanged in its central claim: it reads local files, writes local files, and every change is traceable to the export or an accepted decision.
assist is a separate, opt-in command. It reads the plan artifacts and writes run.json, suggestions.json, review.html and, for triage, brief.md. It never edits generated output. assist apply writes wp-migrate-core.decisions.json; inspect, convert, report and verify read it with --decisions. The migration commands still make no network requests.alt is filled; an existing alt="" is left alone. One image referenced from twenty pages gets one suggestion. Text context comes first, so the job does not require a vision-capable endpoint.convert; component names remain developer work.--export, fixes malformed block markup, and rejects any patch that introduces a new finding code or removes too much page text. convert --decisions runs the same validation before applying a hand-edited patch.wp-migrate-core.ai.json names an environment variable through apiKeyEnv; literal Authorization headers and remote plain-http endpoints are refused. The provider is a native-fetch OpenAI-compatible client with JSON-schema handling, capability fallbacks, retries, timeouts, request/token limits and a local response cache. schema --ai prints the config schema.apiStability is stable. The migration config stays at 0.7; AI settings and artifacts have their own 0.1 schema versions, so the frozen migration format did not move.assist, assist apply and convert --decisions.External download
pages collection. Keeps Nuxt and Pruvious on the same minor version and pins the editor extensions to their compatible core version.WP_MIGRATE_IMPORT_TOKEN for Pruvious imports. Re-imports preserve dashboard edits, clear removed relationships and count rejected updates as failures. The command exits unsuccessfully when records fail.public/ as well as importing them into the media library. Download references no longer hide image alt-text findings.WP_MIGRATE_IMPORT_REPORT as a file path in both importers and reports malformed drop arrays instead of crashing without a report.0.10 made the Payload destination something you can open and edit. This release makes it a destination, not a Payload script: the second CMS arrives, the first one's re-run promises become true for media as well as text, the menu becomes something you edit in the admin, and the files that are not images — PDFs, price lists, downloads — come along instead of being left on the old host.
convert --target pruvious writes a Nuxt app with the pruvious module: one collections/<name>.ts per content type plus media, categories, tags, authors and navigation, a nuxt.config.ts, the drop and a generated importer under import/ and server/, the redirect map as Nitro middleware, and the same migration/ review artifacts the other targets write. The importer runs inside the app through Pruvious's own collection API and writes the same migration-import.json, so verify --import-report reads either destination the same way. This is the destination boundary's second proof: the new target adds no rule to core.ts.hero.jpg re-uploads it instead of reporting unchanged while the CMS keeps the old image. A record's fields are still the only thing that decides a CMS edit is protected.migration-import.json is written next to the bundle rather than in whatever directory the importer was launched from (WP_MIGRATE_IMPORT_REPORT overrides it), and a drop file that cannot be read is one reported failure instead of a run that stops before writing anything. The Pruvious importer behaves the same way.verify --import-report sees the supporting records. Missing media, author and taxonomy records the plan expects are now findings, not silence, and a report whose summary.planned disagrees with the records it lists is flagged as IMPORT_SUMMARY_MISMATCH.navigation collection built from the export's menu tree. Payload uses nested array fields; Pruvious stores child entries as JSON in a text field. Unresolved entries keep the reason a human needs. The static target still renders Nav.astro.linked-file media reference, delivered with the site the way an image is, and, on the CMS path, imported as a media record. It no longer becomes a link that 404s after migration.0.11 for the navigation collection in its collections block; the Pruvious manifest is 0.11 from the start. The Astro manifest stays 0.9, and the import report and verification artefact stay 0.9, because neither changed shape.npm run test:payload, which now also covers the re-run media hash and the navigation collection.External download
.env consistently with the admin app, including the database URL and secret.src and alt attributes instead of confusing them with data-src and data-alt..env, linked images, and draft-inclusive verification. Test subprocesses no longer inherit an external database URL or Payload secret.0.9.0 handed you a Payload bundle; this release hands you the app. The bundle now carries the Next + Payload admin host, so npm install and npm run dev open /admin instead of telling you to bring your own project. Images in the body become media nodes rather than notes, and the round trip that was run by hand for 0.9.1 is now a check anyone can re-run.
convert --target payload writes the app/(payload) route files, the tsconfig.json Payload's own CLI reads, .env.example, .gitignore, and a next.config.mjs that wraps the project in withPayload. The generated package.json gains dev, build, start and generate:importmap scripts. Payload adds its own users collection for admin accounts and the first one is created in the browser; WordPress authors are still an authors collection rather than logins.contentReview entry instead of pointing at a record that will never exist. A delivered image is no longer also left in the review queue.npm run test:payload is the round trip. The check installs Payload 3.90.2 and Next into a temporary workspace, imports a generated bundle into SQLite, re-imports it, proves a source change updates while a CMS edit is protected, asserts the body image is a real populated media node, runs verify --import-report, and builds the generated admin app. It is opt-in because it installs packages; the default suite still never boots Payload.@payloadcms/* packages are written as 3.90.2, the release the round trip runs against, because @payloadcms/next declares its payload peer as that exact version. The manifest, the bundle's package.json and the README all record it.payload.config.js. Payload's CLI only discovers payload.config.js or payload.config.ts, so the .mjs name meant payload generate:importmap and friends could not find the config. A bundle written before this release needs the file renamed.0.10 for the config and admin entries in its destination block. The Astro manifest stays 0.9, the import report stays 0.9 and the verification artefact stays 0.9, because none of those changed shape.External download
wordpressTerms without creating jobs for nonexistent collections. Refuses conflicting collection names before writing a bundle and handles numeric module names and comment delimiters in site titles.IMPORT_REFERENCE_UNRESOLVED warnings, and unavailable demo targets fail before writing output.The tool stops being only an Astro renderer. Destinations now have a kind — a static site or a CMS — and the first CMS destination, Payload, turns the export into collections, a content and media drop, and a generated importer you run inside the destination project. verify can then answer a third question: not just "is the content in the front end?" but "is it in the CMS, and what still needs a human there?".
The direction is yours: people leaving WordPress still need somewhere to edit their website. The WXR parsing, media handling, URL and link inventories, redirects, repair queue and verification all apply across destinations, so the work here is the destination boundary plus the first target behind it. Pruvious and Ghost are planned next; Svelte stays a real but non-priority static target, and a bare Next.js or Nuxt renderer stays parked behind the CMS work.
TargetAdapter gains kind: "static-site" | "cms", and the CLI, the report and the manifest now say which one a target is instead of implying every target writes a site. --target payload is implemented; next, nuxt and svelte are declared and refuse with a reason. The manifest's targets block and the report's target cards are generated from one registry, so they cannot disagree.convert --target payload writes payload.config.mjs, one collection module per content type plus media, categories, tags and authors, a content and media drop under import/, a generated importer, and a redirect map in the shape a Next front end uses (alongside the Netlify, Vercel, nginx and Apache files). wp-migrate-core itself still reads and writes local files and makes no network requests; the importer is the only thing that talks to a database, and it is yours to run.draft/pending become a Payload draft; future and private keep their own wordpressStatus. Authors become an authors collection rather than Payload users, because dc:creator is a WordPress login and creating login accounts is a deployment decision.contentReview entries instead of being guessed at, and sourceHtml keeps the exact export HTML so nothing is lost.{ "$ref": ... } markers, and uploaded by the importer so Payload owns the file, its sizes and its alt text. A linked or remote file has no local bytes, so it is left out and any reference to it is reported as unresolved rather than inventing an empty media record.wordpressSourceId the manifest already uses (wp:post:20), and uses an import hash to tell "the source changed" apart from "somebody edited this in the CMS". A record that changed is updated; a record edited in the CMS is skipped and reported as record-modified rather than overwritten.verify gains --import-report. Read the migration-import.json a generated importer wrote, and the launch gate gains an import check: a planned record the report never mentions is IMPORT_RECORD_MISSING, a record the importer failed on is a blocker (IMPORT_RECORD_FAILED), a skipped record is IMPORT_RECORD_SKIPPED, and a content id the plan does not carry is IMPORT_RECORD_UNKNOWN. The report can stand alone if you only want to know whether the CMS import landed, or join a build check so one run answers both questions.0.9 for the destination block and the new targets, and the verification artefact moves to 0.9 for the import evidence. Media stays 0.7, links 0.3, coverage 0.4, redirects 0.2, archives and menus 0.8, because none of those changed shape.External download
.xml feeds as files and includes them in route coverage. Feeds cannot overwrite another generated public file.The handoff now carries the parts of a WordPress site it used to leave behind, and verify can say whether the result is ready for DNS. Custom post types, navigation menus and archive pages come across instead of being dropped, and the publishing gate reads the build itself: media that never arrived, links that lead nowhere, canonical URLs, robots.txt and the sitemap.
src/content/, and the manifest records what the export carried and what this run did with it. Types that are not content, such as revisions and customizer drafts, are reported rather than passed over, and postTypes.include / postTypes.exclude decide anything you want decided differently.Nav.astro the layout renders, migration/menus.json records the tree, and an entry nothing serves is reported (MENU_TARGET_UNRESOLVED).ARCHIVE_ROUTE_ASSUMED) rather than presenting as fact.archives.postsIndex), pagination (archives.perPage), date archives (archives.dates) and RSS feeds (archives.feeds). Feeds are written as static files, and the address WordPress served gets a redirect rule to reach them.archives.attachmentPages: "ignore".verify gains the launch gate: it checks that every copied media file is present in the build and is not empty (MEDIA_MISSING_IN_BUILD, MEDIA_EMPTY_IN_BUILD), that internal links resolve to a route, a rule or a build file (LINK_TARGET_MISSING_IN_BUILD), that pages carry a canonical URL (CANONICAL_MISSING), that robots.txt does not disallow crawling (ROBOTS_BLOCKED) and that the sitemap lists every generated route (SITEMAP_INCOMPLETE).--publish-ready to convert and demo: an indexable layout with a canonical URL per page, and a robots.txt that allows crawling and names the sitemap. The private default is unchanged.checks), and counts the unchecked ones separately. A check that needs the build output is never reported as passed when only a crawl report was supplied.manifest.json and the new archives.json and menus.json use schema 0.8; the verification artefact moves to 0.8. Media stays 0.7 and links stay 0.3, because neither changed shape.External download
You can now bring images into the generated Astro site. Use --uploads with a local copy of your WordPress uploads directory, or --media-base if a CDN already serves your files. The migration report shows which files were copied, which stay remote, and which still need attention.
There's also a config file for the decisions you used to make by hand: choosing a route for a ?p=123 permalink, leaving out a page, or accepting a finding you've already reviewed. Keep it beside the export to reuse those decisions on the next run.
public/wp-content/uploads/, preserving their upload paths. Uploads listed in --live-urls are included too; --copy-unused-media adds the remaining attachments.srcset, and fills in missing alt text and dimensions from the export. Decorative images with alt="" keep it. If a resized image is unavailable, the original file can stand in for it.wp-migrate-core.config.json beside the export, or a file passed with --config. It supports routes, exclusions, reviewed findings, site details, media sources and the failure threshold.ignored, labels them in the report, and leaves them out of the counts used by --fail-on. Unmatched config entries produce a warning.0.7; local uploads directory paths are omitted.External download
The npm package homepage now points to wp-migrate-core on NikoCodes. No changes to migration behavior.
External download
Check how your rebuilt site responds to visitors and crawlers before moving DNS. This release adds saved SSRWire audits to the existing content checks, including a comparison with your WordPress site.
--ssrwire-report <file> to verify alongside --html-dir or --routelint-report to check response status, metadata and indexing directives. Add --ssrwire-baseline <file> to see what changed since the source audit. verify reads these files locally.npm run check:source and npm run check:preview. Capture the source report while WordPress is still online. The checks cover the first 50 planned routes; you can extend the lists.og:image, with the affected routes and crawler profiles. Shared problems are grouped to keep the repair list readable.public/sitemap.xml and sets Astro's site URL when the export provides one. Sitemap dates omit invalid WordPress values and avoid guessing a timezone.0.6, with delivery and launch sections and publishing blocker/warning counts. It preserves metadata presence and numeric timings without copying page text or metadata values, and keeps differing crawler results separate.The generated site still starts with noindex, nofollow and a robots.txt crawl block. Remove both before publishing. verify detects the page's indexing directives; review robots.txt separately.
External download
Once you've built your new site, verify helps you check that the WordPress content made it across. Point it at your build folder or a saved RouteLint report; everything is read locally.
wp-migrate-core verify export.xml --html-dir dist, or use --routelint-report report.json for a site you've already crawled. Both Astro HTML output formats are supported, and crawl reports can use a staging domain.--fail-on blocker to stop on missing content, or --fail-on warning to catch changed text too. The results are saved to migration-verification.json without copying page text into the report.External download
This release closes the URL loop. It compares the URLs the live site already serves with the routes this plan generates, and it writes the redirect rules the plan calls for in the formats common hosts expect.
--live-urls <file> to inspect, convert, report and demo. It reads a downloaded XML sitemap, a sitemap index, or a plain list with one URL or path per line, and the option can be repeated to check several files at once. It reads local files only: a URL on the command line is refused, and nothing is fetched.migration/coverage.json (schema version 0.4), a coverage section in the HTML report, a coverage line in the CLI summary, and a coverage block in --json.--fail-on warning can gate a migration that would drop URLs, and one warning when the supplied source lists no page URLs at all, such as a sitemap index passed on its own./guides/tap/ does not silently cover /guides/tap.migration/redirect-rules/netlify/_redirects, vercel/vercel.json, nginx/redirects.conf and apache/.htaccess. Netlify slash-only changes defer to Pretty URLs; Vercel patterns avoid matching their own destinations. nginx and Apache match original request paths, preserving encoded separators and repeated slashes. Configuration delimiters, Unicode, bare percent signs and replacement syntax are escaped per host, and no file is written when the plan needs no rule.0.4 with a coverage block. Media and the redirect map stay at 0.2, and the link inventory stays at 0.3.0.4.0 and adds screenshots captured from the fictional demo report.External download
migration/links.json.--keep-source-links and the library's { rewriteLinks: false } option retain source hrefs while keeping proposed rewrites visible.0.3; media and redirect inventories remain at 0.2.External download
This release adds a media inventory and a URL/redirect map to help plan a WordPress-to-Astro migration. Both use only the WXR export; migration commands make no network calls and do not download media or publish redirects.
--json. Generated projects include migration/media.json and migration/redirects.json; these files and the manifest use schema version 0.2 independently of the package version.External download
This release adds CLI scan controls and machine-readable results for local review and CI.
--include-drafts through to the parser so CLI scans can include drafts, pending, private, and other non-published posts and pages.--json to inspect, convert, report, and demo, reporting generator identity, scan settings, summary counts, sanitized issues, output paths, and failure status without the source URL or raw issue evidence.--fail-on none|warning|blocker to set a failing exit status for issues at or above the chosen severity after writing output. The default none preserves existing behavior.External download
This release improves CLI argument handling and makes the current release the default npm install.
latest and updates the installation instructions.--out and --target before reading an export or writing output.--version / -v and supports --help / -h after a command without requiring an input file.External download
This release makes inspection safer when an export or output path is not quite what the CLI expects.
External download
The first public demo: inspect a WordPress WXR export, surface unsupported migration work, and generate a deliberately private Astro handoff for human review.
External download