Data Migrator
Securely migrate status-preserving WordPress posts and lightweight page structures, terms, metadata, and media between sites through authenticated REST endpoints and a resumable background queue.
by Mubeen Hassan · github.com/mubeenhassan-dev/data-migrator-plugin
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/mubeenhassan-dev/data-migrator-plugin/archive/refs/heads/main.zipReadme
Data Migrator
Data Migrator is a custom WordPress plugin for migrating data directly from one WordPress website to another through authenticated REST API communication.
The plugin is designed around a Source / Destination architecture: the old website exposes protected read-only migration endpoints, while the new website creates resumable migration jobs, imports data in batches, tracks mappings, and provides live progress and migration history.
Current version: 0.8.3
Requires WordPress: 6.0+
Requires PHP: 7.4+
License: GPL-2.0-or-later
Current migration support
Users
Data Migrator can migrate WordPress user accounts while preserving the user's existing login password.
The Users migration includes:
- Username and email address
- Display name and nicename
- Website URL and registration date
- User status
- Current-site roles and capabilities
- User level
- Non-authentication profile/user metadata
- The existing WordPress
user_passhash - Source-to-destination user ID mappings
The password is never requested, exposed, or transferred in plaintext. The destination stores the source WordPress password hash without hashing it again, allowing migrated users to continue signing in with the same password.
For security, the following authentication state is intentionally not migrated:
- Active WordPress sessions
session_tokens- WordPress Application Passwords
- Password reset/activation keys
- Network super-admin grants
- Data Migrator's own internal metadata
When Users and Posts are migrated together, Users run first so imported posts can map back to their original migrated authors. A configurable destination-author fallback remains available when an author cannot be mapped.
WooCommerce Products
WooCommerce Products are a first-class migration type. WooCommerce must be active on both the source and destination websites.
The administrator can choose between two product-data modes:
- Products & variations only — recommended: recreates the standard WooCommerce catalog without copying arbitrary plugin/custom metadata. It includes product type, name/description, SKU, prices and sale dates, inventory/stock settings, tax/shipping fields, dimensions, purchase note, catalog visibility, categories, tags, product brands when registered, shipping class, global/custom attributes, default attributes, featured/gallery images, downloadable files, upsells, cross-sells, grouped-product relationships, and variable-product variations.
- Full Product Data: includes everything above plus non-core product and variation metadata/custom fields, term metadata, and registered custom product taxonomies. Custom meta keys are preserved exactly. Plugin-specific data is most useful when the same extension/custom code exists on the destination.
Simple products are imported directly. Variable products use a separate paginated Variations stage so large catalogs do not embed every variation in a parent-product response. When the selected source products have no variations, the Variations stage is omitted.
Product images, gallery images, product-category thumbnails, and local downloadable files reuse the shared Media queue. After media has finished, Product setup connects migrated attachments, downloads, upsells/cross-sells, grouped children, category thumbnails, and variable-product synchronization.
Source-to-destination mappings and SKU matching make repeat migrations duplicate-safe. Existing products can be updated or skipped through the shared duplicate strategy.
Product reviews/orders/customers are not part of the Products migration in this release. Extension-specific product types can be recreated only when the extension providing that type is available on the destination; Full Product Data preserves their non-core metadata but cannot reproduce behavior for a missing extension.
Posts
Posts can preserve:
- Title
- Content
- Excerpt
- Slug
- Original WordPress status
- Publish and modified dates
- Categories and tags
- Author mapping
- Featured images
- Embedded media and galleries
- Attachments
- Public custom fields
- Common Yoast SEO, Rank Math, and AIOSEO metadata
Supported post statuses include Published, Draft, Pending Review, Private, and Scheduled.
Pages
Pages intentionally use a lightweight structural migration because the destination website may use a different theme, builder, or page design.
The page importer migrates:
- Title
- Slug
- Original date
- Original status
- Parent/child relationships
It does not replace destination page content, Gutenberg blocks, Elementor/design data, templates, SEO metadata, or custom fields. When a previously imported page is updated, existing destination page content remains intact.
Terms and media
Post migrations include the supporting taxonomy stages for categories and tags, including category parents.
Media options include:
- Media used by selected posts — recommended
- Featured images only
- Entire source Media Library
- Skip media
Media processing uses batching, duplicate detection, mappings, progress tracking, and background workers.
Migration scope and stages
The worker builds the stage sequence from the import types actually selected by the administrator. Unselected migration types are not processed or displayed as fake stages.
Examples:
| Selected import | Stages |
|---|---|
| Users | Users |
| Pages | Pages |
| Products (simple only) | Products → Media → Product setup |
| Products (with variations) | Products → Variations → Media → Product setup |
| Users + Pages | Users → Pages |
| Posts, media skipped | Terms → Posts → Finalize → Verify |
| Posts with media | Terms → Posts → Media → Finalize → Verify |
| Users + Products + Posts + Pages | Users → Products → Variations* → Terms → Posts → Pages → Media → Product setup → Finalize → Verify |
* The Variations stage is omitted when no selected source products have variations.
Source and destination modes
The plugin can run in:
- Source mode — exposes authenticated migration endpoints
- Destination mode — connects to a source and imports data
- Both mode — enables both responsibilities on the same installation
The responsibilities remain separated internally even when Both mode is selected.
Connection flow
The destination uses a single first-time Save & Connect action.
- Enter the source URL, WordPress username, and Application Password.
- Click Save & Connect.
- Data Migrator performs a lightweight authenticated
/statusAPI health check only. - Credentials are encrypted and stored only if that check succeeds.
- After a connection is saved, the fields are locked and Test Connection becomes available.
Connection testing does not fetch products, posts, pages, users, media, or migration totals. Product/WooCommerce capability and counts are checked only when Products is selected while creating a migration job.
Forget connection uses a custom confirmation modal and removes the saved username and encrypted Application Password. The source URL remains available for convenience.
Users endpoint security
User migration uses dedicated source endpoints:
/wp-json/data-migrator/v1/users/status
/wp-json/data-migrator/v1/users
The Users endpoints:
- Require an authenticated administrator with
manage_options - Use the dedicated status endpoint for permission/count preflight
- Return user data in batches
- Mark responses as non-cacheable
- Never put password hashes into migration activity logs
- Never persist password hashes in migration queue payloads
HTTPS and local development
Public source websites must use HTTPS for Users export.
Supported examples:
https://oldsite.com → https://newsite.com
https://oldsite.com → http://newsite.local
http://oldsite.local → http://newsite.local
HTTP user export is allowed only for recognized local-development sources. WordPress Application Password authentication must also be available on that local source; for normal WordPress local development this commonly means setting WP_ENVIRONMENT_TYPE to local or using local HTTPS.
Do not enable relaxed local/private URL or SSL-verification settings on production destinations.
Background processing
Data Migrator uses:
- Action Scheduler when its API is available
- WordPress Cron as a fallback
- Live-page worker requests to improve throughput while the migration dashboard is open
Migration state is stored in the database, so refreshing or reopening the admin page does not lose the job.
The migration system is designed to remain:
- Resumable
- Batch-based
- Duplicate-safe
- Mapping-aware
- Memory-conscious
- Traceable through activity logs
Live migration dashboard
While a migration is active, the dashboard shows:
- Overall progress
- Live elapsed time
- Current stage
- Current item/operation
- Imported count
- Updated count
- Skipped count
- Failed count
- Recent activity
- Pause / Resume / Cancel controls when appropriate
The elapsed timer runs client-side every second and is synchronized against the server's elapsed duration. It no longer waits for a worker/status request before visually updating.
When a migration becomes Completed, Failed, or Cancelled, live polling and activity DOM updates stop.
Migration History
Migration History is intentionally separate from the live processing dashboard.
The history table displays completed and previous migration records without starting the live poller. Each row provides View logs, which opens a custom log browser modal.
The logs modal includes:
- Scope-aware title such as
Log #27 - Users + Posts Import - Total log count
- Matching-result count
- Search
- Level filter
- Log type filter
- 25 / 50 / 100 per-page selection
- AJAX pagination with First / Previous / numbered pages / Next / Last
- Browser-local timestamps
- One controlled modal-body scroll region
Logs are loaded on demand only when the modal is opened.
History cleanup and safe rollback
Data Migrator treats history deletion and imported-content rollback as two different operations.
- Delete history removes one terminal migration record together with its logs, queue records, and rollback audit data. Destination content is not deleted.
- Clear history performs the same history-only cleanup for all completed, failed, and cancelled migrations while active migrations remain untouched.
- Rollback content selectively removes destination objects that the selected migration actually created. Rollback runs through a background queue (Action Scheduler when available, WordPress Cron fallback) while AJAX only monitors deleted, protected/shared, failed, and remaining counts.
Starting with v0.8.0, every imported object receives a migration ownership audit record. Objects that already existed and were only updated or skipped are recorded as protected rather than owned. If an object created by one migration is later reused by another migration, rollback treats it as shared and leaves it in place.
Rollback is dependency-aware: product variations are removed before products, posts/pages before media and taxonomies, and users last. Taxonomy terms are deleted only when they are no longer assigned, and user accounts are protected when deleting them would be unsafe. The feature is deliberately a safe delete of migration-created objects, not a snapshot restore of pre-existing objects.
Starting with v0.8.2, rollback execution is independent of the Migration History page. Starting a rollback queues a dedicated background worker. If Action Scheduler is available it is used automatically; otherwise Data Migrator schedules WordPress Cron events. The modal polls read-only rollback status through AJAX, so it can be closed and reopened without stopping the rollback. The worker processes one dependency-aware batch at a time, uses a migration-specific lock to avoid concurrent deletion workers, and retries unexpected worker failures with a bounded retry count.
Migrations created before v0.8.0 do not have reliable ownership records, so the UI marks them Rollback unavailable instead of guessing and risking deletion of pre-existing content.
Duplicate handling
Data Migrator stores source-to-destination mappings so repeat migrations do not blindly create duplicate content.
For Users, the importer can update or skip an existing account. Identity-conflict protection prevents an unsafe merge when the source username and source email independently match different destination accounts.
For Products, Posts, and Pages, mappings allow future runs to update previously migrated records according to the selected duplicate strategy. Products can additionally match an existing non-variation destination product by SKU when no Data Migrator mapping exists.
Installation
- Install and activate Data Migrator on both WordPress websites.
- On the old website, go to Data Migrator → Settings and choose Source or Both.
- Create a WordPress Application Password for the account Data Migrator will use.
- Use an administrator account when migrating Users. If migrating Products, activate WooCommerce on both websites.
- On the new website, choose Destination or Both.
- Open Data Migrator → Import Data.
- Enter the source URL, username, and Application Password.
- Click Save & Connect.
- Select Users, Products, Posts, Pages, or a combination.
- Configure the relevant filters/options and click Create migration job.
For maximum throughput on a local-development destination, keep the live migration page open while the job runs.
Rollback is different: once queued, rollback processing runs in the background and does not require the Migration History modal or browser tab to remain open. Action Scheduler is used when available, otherwise WordPress Cron processes rollback batches.