WP-CLI
Drive WordPress from the shell: core, plugin and theme commands, database work, wp eval, and registering your own commands.
WP_CLI::add_command( string $name, callable|string $callable, array $args = array() )WP-CLI boots WordPress without a web request and then hands you its API from the shell. Anything you can do in the admin you can script, run over SSH, put in a deploy step, or repeat across a hundred sites in a loop — and it works when the admin is broken, which is often exactly when you need it.
The command families
wp core download|update|update-db|version— core files and then the database schema, in that order.wp pluginandwp theme—install,activate,update --all,list --status=active.wp plugin install akismet --activateis the shorthand for aninstallfollowed by a separatewp plugin activate akismet.wp db export|import|query|cli— a wrapper aroundmysqldumpand the MySQL client that reads credentials fromwp-config.php, so you never retype them.wp db exportwith no filename writes{dbname}-{Y-m-d}-{hash}.sql.wp search-replace— the URL rewrite that understands serialized data; see the deployment lesson.wp eval 'php', pluswp option,wp post,wp user,wp cronfor one-off surgery.
Global flags and your own commands
--path= selects the install, --url= selects the site on multisite, and --format=json|csv|count shapes output for scripts.
WP_CLI::add_command( $name, $callable ) registers a command. The callback receives ( $args, $assoc_args ) — positional arguments and --key=value arguments — and WP-CLI parses the synopsis and help text out of the docblock above it.
Example
<?php
/**
* Plugin Name: Acme CLI
*/
// WP-CLI defines the WP_CLI constant. Guard so this file is inert on web requests.
if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
return;
}
class Acme_Report_Command {
/**
* Rebuilds the cached sales report for one month.
*
* ## OPTIONS
*
* <month>
* : Month to rebuild, as YYYY-MM.
*
* [--force]
* : Rebuild even when a cached report already exists.
*
* [--format=<format>]
* : Render output in a particular format.
* ---
* default: table
* options:
* - table
* - json
* ---
*
* ## EXAMPLES
*
* wp acme report rebuild 2026-08 --force
*/
public function rebuild( $args, $assoc_args ) {
list( $month ) = $args; // positional arguments, in order
$force = isset( $assoc_args['force'] ); // flags are simply present or absent
if ( ! preg_match( '/^\d{4}-\d{2}$/', $month ) ) {
// error() prints to STDERR and exits with a non-zero status,
// which is what makes a deploy script stop.
WP_CLI::error( "Not a YYYY-MM month: {$month}" );
}
WP_CLI::log( "Rebuilding {$month}..." ); // ordinary output, hidden by --quiet
$rows = acme_rebuild_report( $month, $force );
WP_CLI::success( sprintf( '%d rows written.', count( $rows ) ) );
}
}
// Public methods of the class become subcommands: wp acme report rebuild
WP_CLI::add_command( 'acme report', 'Acme_Report_Command' );When to use it
- A deploy script must update core, run the database migration and clear caches without a browser.
- The admin is white-screening and you need to deactivate a plugin to get back in.
- You have to change one option value on forty sites in a network.
- A colleague needs a database dump of production with the credentials already configured.
More examples
A safe update run
The order matters: core update-db must follow core update, and a database export must precede both. Use this as the body of a deploy script — every command returns a non-zero exit status on failure, so `set -e` stops the run before the next destructive step.
# Back up first — credentials are read from wp-config.php, so no flags needed.
wp db export backups/pre-deploy.sql
# Core files, then the database schema. Skipping update-db leaves the site
# showing the "database update required" screen.
wp core update
wp core update-db
wp plugin update --all
wp theme update --all
# Inspect state without opening the admin.
wp plugin list --status=active --field=name
wp option get siteurl
wp cron event list --due-now
# wp core version reports what is actually on disk, not what the admin claims.
wp core versionwp eval for one-off work
wp eval is the escape hatch for work that has no command: clearing one transient, checking a value, running a backfill. The globals caveat catches everyone once — $wpdb is null inside eval unless you declare it, because the code is executed inside a WP-CLI method rather than at file scope.
# The code runs inside a method, so globals must be declared explicitly.
wp eval 'global $wpdb; WP_CLI::log( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts}" ) );'
# Anything WordPress can do is available, because WordPress is fully loaded.
wp eval 'WP_CLI::log( wp_get_environment_type() );'
wp eval 'delete_transient( "acme_feed" ); WP_CLI::success( "cleared" );'
# For anything longer than one line, put it in a file and run that instead.
wp eval-file scripts/backfill.php
# --skip-plugins / --skip-themes load core only: the fastest way to find out
# whether a plugin is the thing breaking the site.
wp --skip-plugins --skip-themes option get homeLooping over a multisite network
WP-CLI is the only practical way to audit or change a large network — the network admin has no bulk view of per-site plugin state. Use this pattern before a plugin removal, to find which sites actually have it active.
# --url= picks the site; the command otherwise behaves exactly as on a single site.
for url in $(wp site list --field=url); do
echo "== $url"
wp --url="$url" plugin list --status=active --field=name
done
# Network-wide operations do not need --url.
wp plugin update --all
wp site list --fields=blog_id,url,registered --format=csv
Discussion