wp-cli/import-command
WP-CLI command for importing content into WordPress from WXR export files. Adds wp import, restoring the importer functionality removed from WP-CLI core. Automatically detects and runs the matching WordPress importer plugin, making site migrations and content restores scriptable.
wp-cli/import-command is the package behind wp import — the command that ingests a WXR (WordPress eXtended RSS) export. It ships bundled with the WP-CLI phar, so in most environments there is nothing to install; the split repo exists so the command can be versioned independently (wp package install wp-cli/import-command only if you need to pin or patch it).
First three things to do:
wp help import # authoritative option list for YOUR bundled version
wp cli info # confirm WP-CLI/PHP versions and whether it's a phar
wp import --help | head -40
Then, on the target site, before touching a real file:
wp db export pre-import.sql # rollback anchor
wp plugin list --status=active # CPT/taxonomy providers must be active
wp option get home
A minimal first run against a staging site:
wp import wordpress.xml --authors=create --quiet
wp post list --post_type=any --format=count # sanity count
wp term list category --format=count
Where to look in the source: src/Import_Command.php handles argument parsing and author strategy; the actual migration engine is the bundled WP_Import class (the WordPress Importer plugin code vendored into the package). Grep that file for apply_filters / do_action — it is where every extension point you will ever need lives.
The canonical first use case is source site → staging → review → production: export on the old site (wp export --dir=./wxr), import on staging, run the post-processing pipeline below, then promote the database.
1. Script the whole migration, not just the import. A repeatable migrate.sh is the single highest-value artifact:
#!/usr/bin/env bash
set -euo pipefail
SRC_WXR="$1"; OLD_DOMAIN="$2"; NEW_DOMAIN="$3"
wp db export "backup-$(date +%s).sql"
php -d memory_limit=2048M -d max_execution_time=0 "$(command -v wp)" \
import "$SRC_WXR" --authors=create --quiet
wp search-replace "$OLD_DOMAIN" "$NEW_DOMAIN" --all-tables --precise --recurse-objects
wp media regenerate --only-missing --yes
wp cache flush && wp rewrite flush --hard
Note the php -d ... $(command -v wp) trick: wp is itself a PHP script, so you can raise limits for a single invocation without touching php.ini.
2. Be explicit about --authors on every run. --authors=create makes users from the WXR. For client sites where identity matters, build a CSV mapping and pass --authors=mapping.csv — each row pairs a local login with the remote author login from the WXR. Validate the format on a 3-post fixture WXR before trusting it; the mapping parser is unforgiving about headers and blank lines.
3. Multisite = loop with --url. The command imports into whatever site WP-CLI is bootstrapped against:
for site in one two three; do
wp import "site-$site.xml" --url="example.com/$site" --authors=create
done
Remember users are network-level, so --authors=create on ten subsites mints ten network users with the same logins — map instead.
4. Large files: chunk, don't hope. The parser loads and processes the whole document; a 200 MB WXR will die. Split by post (re-export with wp export --max_file_size=..., or slice the XML with a small script) and loop over the resulting files. Keep each chunk under a few MB.
5. Add idempotency via a filter, because the command has none. Re-running duplicates every post (terms are reused by slug, posts are not). Drop this in an mu-plugin and you get content-hash dedupe:
add_filter( 'wp_import_existing_post', function ( $existing, $post ) {
$hit = get_posts([
'post_type' => $post['post_type'],
'title' => $post['post_title'],
'date_query' => [[ 'year' => substr( $post['post_date'], 0, 4 ) ]],
'fields' => 'ids',
'numberposts' => 1,
]);
return $hit ? $hit[0] : $existing;
}, 10, 2 );
6. Transform content on the way in, not after. wp_import_post_data_raw, wp_import_post_data_processed, and wp_import_post_meta let you rewrite statuses, remap post types, drop junk meta, or strip shortcodes from a legacy plugin before anything hits the DB. This is far cheaper than a post-import database cleanup.
7. Treat import as one stage of a pipeline. Typical order: import posts → wp search-replace (handles serialized meta; do not hand-roll this) → assign menus (wp menu location assign primary main-menu) → set the front page (wp option update show_on_front page) → regenerate thumbnails → flush caches/rewrites → verify counts → wp plugin-level smoke test.
8. Use tiny fixture WXRs in dev/CI. A 5-post tests/fixtures/mini.xml plus wp import ... --quiet inside your wp-env bootstrap gives every developer and every CI run a realistic seeded site in seconds. This is where the package earns its keep day-to-day.
9. Programmatic invocation. From a custom command or wp eval-file, WP_CLI::runcommand( 'import file.xml --authors=create', [ 'exit_error' => false ] ) is the supported path; reaching into WP_Import directly works but couples you to an internal class.
The 22 stars are not a health signal. This repo is a mirror of a bundled WP-CLI command; activity follows the WP-CLI release train, not the star count. Read the bundled class-wp-import.php in your installed version rather than the plugin's docs — behaviour and available --skip tokens differ across versions.
No dedupe, no dry run. There is no --dry-run and no re-import protection. Always wp db export first, and prefer "wipe staging, import from scratch" over "fix it in place" for repeatability.
Attachments are sideloaded over HTTP at import time. If the source site is dead, behind basic auth, or on localhost, media silently fails with placeholder posts. Mirror the uploads directory first (rsync or a local HTTP shim) and verify with wp post list --post_type=attachment --format=count. Use --skip=image_resize on huge imports — intermediate-size generation is the common memory killer; regenerate later with wp media regenerate --only-missing.
Register CPTs and taxonomies before importing. Unknown post types still land in the DB as raw strings, but rewrite rules, admin UI, and taxonomy term assignment break in confusing ways. Activate the plugins/mu-plugins first, then import.
Class collision risk. The command bundles its own WP_Import. If the WordPress Importer plugin is also active and declares the same class, whichever loads first wins and your favourite filter may never fire. Check class_exists( 'WP_Import' ) and where it came from when a hook mysteriously stops firing.
Authors defaults are a foot-gun. Omitting --authors makes the importer map by login and fall back to a default user when it can't find one — posts land under the wrong identity and you find out weeks later. --authors=create makes users with auto-generated passwords and the site's default role; assign roles explicitly afterwards. Treat any WXR from an untrusted source as executable input and do not run --authors=create on it.
WXR is not a site backup. It carries posts, terms, comments, and attachments — not options, widgets, customizer settings, plugin config, user passwords, roles, or capabilities. show_on_front/page_on_front and menu locations are always manual. Post IDs change, so shortcodes, meta, and ?p=123 links referencing source IDs are broken and search-replace will not save you.
WP_IMPORTING is defined during the run, so plugins that branch on it (indexers, search sync, some caching layers) behave differently than in normal requests. Always wp cache flush, wp rewrite flush, and rebuild any search index after.
Debugging a bad import: add --debug, then compare wp post list --post_type=any --format=count and wp term list <tax> --format=count against the source; look for orphaned _wp_attached_file meta (wp db query "SELECT post_id FROM wp_postmeta WHERE meta_key='_wp_attached_file' AND post_id NOT IN (SELECT ID FROM wp_posts)") and for nav_menu_item posts whose parents never resolved.
Best extension point: wp_import_existing_post (skip/redirect duplicates) and wp_import_post_data_processed (the last clean shot at a post array before wp_insert_post). Run grep -n "apply_filters\|do_action" vendor/wp-cli/import-command/.../class-wp-import.php once — that list is your entire customization surface.
How can I help you explore Laravel packages today?