npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

payload-wordpress-migrator

v0.0.24

Published

A PayloadCMS plugin for WordPress migration - migrate and manage WordPress content directly in your Payload admin dashboard

Readme

Payload WordPress Migrator

A PayloadCMS plugin for migrating WordPress content — manage migrations directly from the Payload admin dashboard.

Features

  • Migration Dashboard — real-time progress tracking in the Payload admin UI
  • WordPress REST API Integration — secure connectivity with Application Passwords
  • Job Management — create, pause, resume, retry, rollback, and dry-run migration jobs
  • Content Type Discovery — automatic detection of posts, pages, media, categories, tags, custom post types
  • Field Mapping — visual mapping interface with dot-notation support
  • Media Migration — auto-import featured images and content media with duplicate detection
  • Gutenberg to Lexical — converts WordPress blocks to PayloadCMS Lexical rich text
  • Batch Processing — configurable concurrency with background execution
  • Access Control — configurable role-based access for migration endpoints

Installation

npm install payload-wordpress-migrator

Quick Start

Add the plugin to your Payload config:

import { buildConfig } from 'payload'
import { payloadWordPressMigrator } from 'payload-wordpress-migrator'

export default buildConfig({
  plugins: [
    payloadWordPressMigrator({
      collections: {
        posts: { wpPostType: 'post', enableBlocks: true },
        pages: { wpPostType: 'page', enableBlocks: true },
        media: { wpPostType: 'media' },
      },
      enableMediaDownload: true,
      migrationBatchSize: 25,
    }),
  ],
})

WordPress Setup

Application Password:

  1. In WordPress admin, go to Users > Profile
  2. Scroll to Application Passwords
  3. Enter a name (e.g., "Payload Migration") and click Add New Application Password
  4. Copy the generated password — use this as WORDPRESS_APP_PASSWORD

Requirements:

  • WordPress 5.6+ (for Application Passwords)
  • REST API enabled (default in most installations)
  • User with appropriate permissions for content access

Configuration

type PayloadWordPressMigratorConfig = {
  // WordPress Connection
  wpSiteUrl?: string // WordPress site URL
  wpUsername?: string // WordPress username
  wpPassword?: string // Application password (not the login password)

  // Collections
  collections?: Partial<Record<CollectionSlug, WordPressCollectionMapping>>

  // Plugin Control
  disabled?: boolean // Disable plugin (schema still added for DB consistency)
  disableDashboard?: boolean // Hide dashboard UI

  // Performance
  migrationBatchSize?: number // Items per batch (default: 10)
  migrationConcurrency?: number // Parallel items within a batch (default: 1)
  wpRequestDelay?: number // Delay in ms between WP API requests (default: 0)

  // Media
  enableMediaDownload?: boolean // Download files from WordPress (default: false)
  maxMediaFileSize?: number // Max file size in bytes (default: 10MB)
  mediaUploadPath?: string // Custom upload directory for media files
  allowedMediaTypes?: string[] // Allowed MIME types
  allowSelfSignedCerts?: boolean // Allow self-signed SSL (dev only)

  // Access Control
  access?: (args: { req: PayloadRequest }) => boolean | Promise<boolean>
}

type WordPressCollectionMapping = {
  wpPostType: string // WordPress content type slug
  fieldMapping?: Record<string, string> // WP field path → Payload field path
  enableBlocks?: boolean // Convert Gutenberg blocks to Lexical
  customFields?: string[] // ACF field names to migrate
  importContentMedia?: boolean // Auto-import images from content HTML
  disableHtmlConversion?: boolean // Skip HTML-to-Lexical conversion
}

Plugin Architecture

Collections

The plugin creates a wordpress-migration collection for job management and adds migratedFromWordPress metadata fields to configured target collections:

  • migratedFromWordPress.wpPostId — original WordPress post ID
  • migratedFromWordPress.wpPostType — WordPress content type
  • migratedFromWordPress.migrationDate — migration timestamp

API Endpoints

POST   /api/wordpress/test-connection      # Test WordPress connectivity
GET    /api/wordpress/migration-summary    # Dashboard data with caching
POST   /api/wordpress/discover-content     # Content type discovery
GET    /api/wordpress/migration-jobs       # Job status
POST   /api/wordpress/migration-jobs       # Start/retry/resume/rollback jobs
PUT    /api/wordpress/migration-jobs       # Pause/resume jobs
DELETE /api/wordpress/migration-jobs       # Delete jobs
POST   /api/wordpress/content-fields       # Analyze WordPress fields
GET    /api/collections/:slug/fields       # Analyze PayloadCMS fields

Supported Content Types

| Content Type | Features | Notes | | --------------------- | ---------------------------------------------------- | ----------------------------------- | | Posts | Content, metadata, categories, tags, featured images | Full Gutenberg block conversion | | Pages | Content, metadata, featured images, custom fields | Hierarchical structure support | | Media | File downloads, metadata, alt text, captions | MIME type filtering and size limits | | Categories | Names, descriptions, hierarchy, counts | Taxonomy relationships | | Tags | Names, descriptions, post associations | — | | Users | User data, roles, capabilities, avatars | Permission mapping | | Custom Post Types | Auto-discovery, custom fields, metadata | ACF integration |

Media Migration

Two modes:

  1. Metadata Only (default) — migrates titles, descriptions, alt text, and URLs
  2. Full File Migration — downloads files from WordPress and uploads to PayloadCMS
payloadWordPressMigrator({
  enableMediaDownload: true,
  maxMediaFileSize: 50 * 1024 * 1024, // 50MB
  allowedMediaTypes: [
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/webp',
    'application/pdf',
    'video/mp4',
  ],
})

Auto-import features:

  • Featured images — detected via WordPress _embed data, downloaded and linked
  • Content media — scans HTML for wp-content/uploads URLs, imports automatically
  • MediaBlock conversion — HTML <img> tags converted to PayloadCMS MediaBlocks

Recommended workflow: Migrate posts/pages only — media referenced in content will be auto-imported.

Advanced Configuration

Complete Example

payloadWordPressMigrator({
  wpSiteUrl: process.env.WORDPRESS_API_URL,
  wpUsername: process.env.WORDPRESS_USERNAME,
  wpPassword: process.env.WORDPRESS_APP_PASSWORD,

  collections: {
    posts: {
      wpPostType: 'post',
      enableBlocks: true,
      importContentMedia: true,
      customFields: ['_yoast_wpseo_title', '_yoast_wpseo_metadesc'],
      fieldMapping: {
        'title.rendered': 'title',
        'content.rendered': 'content',
        'excerpt.rendered': 'excerpt',
      },
    },
    pages: { wpPostType: 'page', enableBlocks: true, importContentMedia: true },
    media: { wpPostType: 'media' },
    categories: { wpPostType: 'category' },
    products: {
      wpPostType: 'product',
      customFields: ['_price', '_stock_status', '_featured'],
    },
  },

  migrationBatchSize: 25,
  migrationConcurrency: 3,
  enableMediaDownload: true,
  maxMediaFileSize: 50 * 1024 * 1024,
  allowSelfSignedCerts: process.env.NODE_ENV === 'development',

  // Restrict to admin users only
  access: ({ req }) => req.user?.role === 'admin',
})

Field Mapping

Dot-notation paths are supported:

fieldMapping: {
  'title.rendered': 'title',
  'content.rendered': 'content',
  'meta._yoast_wpseo_title': 'seo.title',
  'acf.hero_image': 'hero.image',
}

Programmatic API

The plugin exports utilities for scripted migrations outside the dashboard.

WordPress Client

import { WordPressClient } from 'payload-wordpress-migrator/dist/utils/wordpress/index.js'

const client = new WordPressClient({
  wpSiteUrl: 'https://example.com',
  wpUsername: 'admin',
  wpPassword: 'xxxx xxxx xxxx xxxx',
})

const contentTypes = await client.discoverContent()
const fields = await client.fetchContentFields('post')

HTML to Lexical

import { convertHtmlToLexical } from 'payload-wordpress-migrator/dist/utils/lexical/index.js'

const lexicalContent = convertHtmlToLexical('<p>Hello <strong>world</strong></p>')

Content Transformation

import { transformWordPressContent } from 'payload-wordpress-migrator/dist/utils/content/index.js'

const payloadData = await transformWordPressContent(
  wpItem,
  'post',
  jobConfig,
  payload,
  pluginOptions,
)

Note: These are internal paths (dist/utils/...). The public package export exposes only the plugin function and config types. Internal APIs may change between minor versions.

Migration Best Practices

Recommended migration order:

  1. Categories and Tags (establishes taxonomy)
  2. Media (creates media library)
  3. Pages (static content)
  4. Posts (blog content with relationships)
  5. Users (author information)
  6. Custom Post Types

Performance tips:

  • Start with migrationBatchSize: 10 for testing, increase for production runs
  • Use migrationConcurrency: 3-5 for faster processing
  • Use dry run mode to preview before committing
  • Use resume for interrupted migrations

Troubleshooting

| Problem | Cause | Solution | | ------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | "Authentication failed" | Wrong credentials, or REST API disabled | Verify you're using an Application Password (not your login password). Confirm the REST API is accessible at https://your-site.com/wp-json/ | | Content types not discovered | WordPress permalinks set to "Plain" | In WordPress Admin > Settings > Permalinks, switch to any structure other than "Plain" | | Custom post types not found | CPT not exposed to REST API | Add 'show_in_rest' => true to your register_post_type() call | | SSL certificate errors | Self-signed or expired certificate | Set allowSelfSignedCerts: true for development. In production, use a valid certificate | | Media download fails | WordPress behind CDN or firewall | Ensure media file URLs (wp-content/uploads/...) are accessible from the server running Payload | | "The following path cannot be queried: migratedFromWordPress" | Target collection not in plugin config | Add the collection to the collections option so the metadata fields get injected | | Large migration runs out of memory | Batch size too high for available RAM | Reduce migrationBatchSize (try 5) and migrationConcurrency (set to 1) | | Lexical content looks wrong | HTML conversion edge case | Set disableHtmlConversion: true on the collection mapping as a workaround, and report the specific HTML that fails | | Duplicate items after resume | migratedFromWordPress.wpPostId field missing | Ensure the collection is listed in the plugin collections config — the field is auto-added | | ACF fields not migrated | Field mapping not configured | Add ACF field names to customFields in the collection mapping, or configure explicit fieldMapping in the job | | "MIME type not allowed" or "File too large" | Media file exceeds limits | Expand allowedMediaTypes and increase maxMediaFileSize in plugin config |

Requirements

  • Node.js ^18.20.2 || >=20.9.0
  • PayloadCMS ^3.29.0
  • WordPress 5.6+ with REST API enabled

License

MIT — see LICENSE for details.


Author: Igor Abdulovic at Brightscout

Repository: GitHub