WP-CLI cookbook
Tested WP-CLI recipes for URL changes, bulk deletion, WooCommerce bulk edits, emergency admin access, and reinstalling core or plugins.
On this page
These are the WP-CLI commands Avunu reaches for most often during migrations, cleanups, and recoveries. Run them from the WordPress root directory (where wp-config.php lives), or add --path=/var/www/example.com/htdocs to point at it.
Before You Run Anything
Run as the web server user, not root, so new files get the right owner:
sudo -u www-data wp .... If you must run as root, WP-CLI requires--allow-root. Avoid it where you can.Back up first.
wp db exportwrites a SQL dump to the current directory.Use
--dry-runon commands that support it (search-replacedoes) and read the counts before running for real.After bulk changes, run
wp cache flushso the object cache doesn't serve stale data.
URLs and Search-Replace
wp search-replace is safe for serialized PHP data (widgets, options, page builder content), unlike a raw SQL REPLACE().
Switch a Site From HTTP to HTTPS
Limit the replacement to your own domain. A bare 'http://' 'https://' also rewrites links to external sites that may not support HTTPS.
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guid --dry-run
wp search-replace 'http://example.com' 'https://example.com' --skip-columns=guidMove a Site to a New Domain
For example, after copying a staging site to production:
wp search-replace 'https://staging.example.com' 'https://example.com' --skip-columns=guid --dry-run
wp search-replace 'https://staging.example.com' 'https://example.com' --skip-columns=guid
wp option get home
wp option get siteurlNotes:
--skip-columns=guidleaves post GUIDs alone. They're permanent identifiers that feed readers rely on, not links.Add
--all-tables-with-prefixto include plugin tables that share the WordPress prefix, or--networkon multisite.If
homeandsiteurlstill show the old URL (for example when they're defined inwp-config.php), fix them withwp option update home 'https://example.com'orwp config set WP_HOME ....
Bulk Deletion
Warning
These commands delete content permanently. --force skips the trash. Take a backup and double-check the post type or taxonomy name first.
Delete All Posts of a Post Type
wp post list --post_type=<POST_TYPE> --post_status=any --format=ids | xargs -r wp post delete --force--post_status=any includes drafts and private posts, which wp post list skips by default. xargs -r avoids "argument list too long" on large sets and does nothing when the list is empty.
Delete All Terms in a Taxonomy
wp term list <TAXONOMY> --field=term_id | xargs -r wp term delete <TAXONOMY>Use the taxonomy's registered name (category, post_tag, product_cat, or a custom one like my_taxonomy).
Pause Cache Purging During Bulk Jobs
Page-cache plugins that purge on every change (LiteSpeed Cache is a common one) can make bulk deletes crawl. Deactivate the plugin for the job, then purge once at the end:
wp plugin deactivate litespeed-cache
wp term list <TAXONOMY> --field=term_id | xargs -r wp term delete <TAXONOMY>
wp plugin activate litespeed-cache
wp litespeed-purge allWooCommerce
Use WooCommerce's CRUD functions (wc_get_products(), wc_get_orders(), $product->save()) rather than update_post_meta() or SQL. They keep the product lookup tables, caches, and HPOS order tables in sync.
Set the Price of Every Product in a Category
wp eval '
foreach ( wc_get_products( [ "category" => [ "<CATEGORY_SLUG>" ], "limit" => -1 ] ) as $product ) {
$product->set_regular_price( "0" );
$product->set_sale_price( "" );
$product->save();
WP_CLI::log( "Updated #" . $product->get_id() . " " . $product->get_name() );
}'For variable products, the price lives on each variation, so loop over $product->get_children() and update each wc_get_product( $variation_id ) the same way.
Delete Orders Older Than a Date
This works whether the store uses HPOS or legacy post storage, and processes orders in batches so it doesn't run out of memory:
CUTOFF=<CUTOFF_DATE> wp eval '
$total = 0;
do {
$ids = wc_get_orders( [
"type" => "shop_order",
"date_created" => "<" . getenv( "CUTOFF" ),
"limit" => 200,
"return" => "ids",
] );
foreach ( $ids as $id ) {
wc_get_order( $id )->delete( true );
}
$total += count( $ids );
WP_CLI::log( "Deleted {$total} orders so far" );
} while ( $ids );
WP_CLI::success( "Deleted {$total} orders." );'Replace <CUTOFF_DATE> with a date like 2023-11-01. Orders created before that date are deleted. delete( true ) deletes each order permanently instead of moving it to the trash.
Warning
Don't select order IDs from wp_posts or wp_wc_orders with SQL and delete them in a loop. With HPOS and compatibility sync, an order exists in both places, and deleting from only one leaves the two out of step. Deleting orders also removes sales history from WooCommerce Analytics.
Users
Create an Emergency Administrator
wp user create <USERNAME> <EMAIL> --role=administratorLeaving out --user_pass makes WP-CLI generate a strong password and print it once, so the password never lands in your shell history. To reset an existing account instead, use wp user reset-password <USERNAME> --skip-email --show-password. It changes the password and prints the new one without emailing the user.
Warning
Remove or downgrade emergency accounts when you're done: wp user delete <USERNAME> --reassign=<OTHER_USER_ID>.
Integrity and Reinstalls
Check for Modified Core and Plugin Files
wp core verify-checksums
wp plugin verify-checksums --allAny file listed as changed or unexpected is worth a look, especially after a suspected compromise. Plugins that aren't hosted on WordPress.org report that no checksums are available.
Reinstall All Plugins
This replaces plugin files with fresh copies at the same version without touching settings:
wp plugin list --status=active --field=name | xargs -r -n1 wp plugin install --force
wp plugin list --status=inactive --field=name | xargs -r -n1 wp plugin install --forcePremium plugins that aren't on WordPress.org will fail. Reinstall those from the vendor's zip. If a broken plugin stops WP-CLI from loading, add --skip-plugins --skip-themes to each wp call. For memory errors, run WP-CLI with php -d memory_limit=512M "$(command -v wp)" ....
Reinstall WordPress Core
Removing wp-admin and wp-includes first clears out any files that were injected into them, then the download restores clean copies of the same version:
VERSION="$(wp core version)"
rm -rf wp-admin wp-includes
wp core download --version="$VERSION" --skip-content --force--skip-content leaves wp-content (themes, plugins, and uploads) alone. Root-level core files like wp-login.php are overwritten, while wp-config.php and any extra files in the web root are kept, so inspect leftover root files by hand.
Warning
Capture the version before deleting wp-includes, because wp core version reads it from there.
Roll Back to an Earlier Core Version
wp core update --version=<VERSION> --forceDowngrading core doesn't downgrade the database schema. Roll back only to undo a very recent update, and restore a database backup if the newer version ran a database upgrade the older one doesn't understand.
Database Checks
wp db commands use the credentials from wp-config.php, so you don't need a MySQL root login:
wp db check # Run CHECK TABLE on every WordPress table.
wp db repair # Repair corrupted tables (MyISAM, or InnoDB where supported).
wp db optimize # Reclaim space after large deletions.To check every database on the server rather than just this site's, run mysqlcheck --all-databases --check as a MySQL administrator.
See Database Optimization Queries for finding what's bloating the database.
Sources
This article is in the public domain (CC0 1.0), code samples included. Use it however helps you.