WP-CLI essentials: the commands you actually use

A curated reference to the WP-CLI commands that matter for day-to-day WordPress management: core updates, plugin and theme control, user operations, database work, search-replace, cron, cache, and the Docker and scripting patterns that tie them together. Covers WP-CLI 2.12.0 on PHP 8.2 through 8.4.

How to use this reference

This article covers the WP-CLI commands I reach for on a daily or weekly basis when managing WordPress sites. It is not the full command list (that lives in the official command reference and covers 46+ top-level commands). It is the subset that handles 90% of real operational work: updates, plugin management, user operations, database exports, URL replacements, cron, cache flushes, and health checks.

Version scope. All commands are verified against WP-CLI 2.12.0 (released May 7, 2025), which requires PHP 7.2.24 or newer and supports PHP 8.4. Where a flag or subcommand was added in a specific version, the version is noted inline.

Contents:

Installing WP-CLI

The recommended method is the Phar archive. Download, make executable, move to a directory in your $PATH:

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
wp --info

The last command prints the WP-CLI version, PHP version, PHP binary path, and wp-config.php path (if run inside a WordPress root). If wp --info works, the install is good.

Updating is one command: wp cli update. It fetches the latest stable Phar and overwrites the existing binary.

Alternative methods: Composer (composer global require wp-cli/wp-cli-bundle), Homebrew on macOS (brew install wp-cli), and OS packages. In Docker, the official wordpress:cli image ships a ready-to-use WP-CLI binary (see Running WP-CLI in Docker).

Global flags worth knowing

Every WP-CLI command accepts these flags. They are not specific to any subcommand, and they solve problems that come up in scripting, debugging, and multisite work:

Flag What it does
--path=<path> Points WP-CLI at a WordPress root other than the current directory.
--url=<url> Targets a specific site in a multisite setup. Required for multisite; ignored on single-site.
--skip-plugins[=<plugins>] Boots WordPress without loading all (or specific) plugins. Saves your session when a broken plugin prevents WP-CLI from running.
--skip-themes[=<themes>] Same, but for themes.
--format=<format> Output as table, json, csv, yaml, ids, or count. The json and ids formats are what you pipe into other commands.
--quiet Suppresses informational messages. Useful in cron scripts where you only want error output.
--debug[=<group>] Prints internal debug output. Invaluable when a command silently fails.
--allow-root Suppresses the "running as root" security warning. See the Docker section for when this is appropriate.

Source: WP-CLI configuration reference.

Core management

# Check if a newer WordPress version is available.
wp core check-update

# Download and install the update.
wp core update

# Run the database upgrade routine (equivalent to visiting /wp-admin/upgrade.php).
wp core update-db

# Verify that core files have not been modified.
# This is the fastest first step when you suspect a compromise.
wp core verify-checksums

wp core verify-checksums compares every core file against the checksums published by WordPress.org. Any modified, added, or missing file is reported. In WP-CLI 2.12.0, the --exclude=<files> flag was added so you can whitelist known customisations (a patched wp-config-sample.php, for instance) without false positives.

wp core download --locale=nl_NL downloads a specific locale. Useful for scripted installs where the language pack needs to be in place before the first boot.

Source: wp core commands.

Plugin management

# List all plugins, their status, and whether updates are available.
wp plugin list

# Install and activate in one step.
wp plugin install woocommerce --activate

# Update every plugin with a pending update.
wp plugin update --all

# Deactivate a plugin without deleting its files.
wp plugin deactivate akismet

# Delete a plugin (removes files; does NOT run uninstall hooks).
wp plugin delete hello

# Check whether a plugin is active (exit code 0 = active, 1 = not active).
# Useful in shell scripts.
wp plugin is-active woocommerce

As of WP-CLI 2.12.0, wp plugin update --all respects the requires and requires_php headers in plugin metadata. If your WordPress or PHP version is too low for a plugin update, the plugin is marked "unavailable" instead of silently failing. The --force-check flag forces a fresh update check against the WordPress.org API, bypassing the transient cache.

For scripting bulk updates with a report, pipe the output:

# List plugins that have updates available, output only slugs.
wp plugin list --update=available --format=ids
# Returns: akismet woocommerce yoast-seo

# Pipe into update (xargs handles the list).
wp plugin list --update=available --format=ids | xargs wp plugin update

Source: wp plugin commands.

Theme management

The pattern mirrors plugins exactly: wp theme install, wp theme activate, wp theme update --all, wp theme delete, wp theme list. The same --force-check flag from 2.12.0 applies.

# Switch to a theme, installing it first if needed.
wp theme install flavor --activate

# Update all themes.
wp theme update --all

One difference: wp theme activate accepts only one theme at a time (you can only have one active theme), whereas wp plugin activate accepts multiple slugs.

Source: wp theme commands.

User management

# Create a user with the administrator role.
# WP-CLI auto-generates a secure password and prints it to stdout.
wp user create jan jan@yoursite.nl --role=administrator

# List all users, showing ID, login, email, and role.
wp user list

# Reset a user's password (sends the password reset email).
wp user reset-password 1

# Update a password directly (avoid this in shared terminals; see note below).
wp user update 1 --user_pass='new-password-here'

# Delete a user and reassign their posts to user ID 2.
# Always use --reassign to prevent orphaned posts.
wp user delete 5 --reassign=2

Shell history warning. wp user create with --user_pass and wp user update with --user_pass write the password into your shell history. On a shared server, that is a credential leak. Use --prompt=user_pass instead, which prompts interactively:

wp user update 1 --prompt=user_pass

The same applies to wp config create --dbpass: prefer --prompt=dbpass or set credentials via environment variables.

Source: wp user commands.

Database operations

# Export the full database to a file with a date stamp.
wp db export backup-$(date +%Y%m%d).sql

# Import from a dump.
wp db import backup-20260408.sql

# Run a raw SQL query.
wp db query "SELECT option_value FROM wp_options WHERE option_name = 'siteurl'"

# Check table integrity.
wp db check

# Optimize tables (useful after mass deletes or WooCommerce order purges).
wp db optimize

# Report database size per table.
wp db size --tables

All wp db commands read credentials from wp-config.php. You never pass database usernames or passwords on the command line.

wp db reset --yes drops and recreates every table. It is the nuclear option. The --yes flag is required precisely because the command is irreversible without a backup.

Source: wp db commands.

Search and replace

wp search-replace is the most consequential single command in WP-CLI. It rewrites strings across the entire database, handles serialised PHP data correctly, and has no undo. The dedicated article on WordPress search-replace after migration covers the full workflow including the Gutenberg JSON-escaped URL trap and per-table scope. Here is the command-level reference.

# Step 1: ALWAYS dry-run first. Shows what would change without writing.
wp search-replace 'https://old.yoursite.nl' 'https://new.yoursite.nl' \
  --all-tables-with-prefix \
  --skip-columns=guid \
  --dry-run

# Step 2: Commit the replacement.
wp search-replace 'https://old.yoursite.nl' 'https://new.yoursite.nl' \
  --all-tables-with-prefix \
  --skip-columns=guid \
  --precise

Flags that matter

Flag Purpose
--dry-run Runs the operation, reports counts, writes nothing. Non-negotiable first step.
--precise Forces PHP-level processing for every row instead of SQL string replacement. Without this flag, serialised data can be silently corrupted when the replacement changes string length.
--all-tables-with-prefix Includes every table that starts with the WordPress prefix, catching plugin tables (WooCommerce HPOS, Yoast, Wordfence) that $wpdb does not register.
--skip-columns=guid Protects the wp_posts.guid column, which must never be changed to avoid breaking RSS feeds.
--network For multisite: runs across all per-site tables.
--report-changed-only Only prints tables where replacements occurred. Cleaner output on large databases.

Source: wp search-replace.

Configuration management

# Generate a wp-config.php from scratch.
wp config create --dbname=mydb --dbuser=user --dbpass=pass --locale=en_US

# Read a constant.
wp config get WP_DEBUG

# Set a boolean constant (--raw means the value is not quoted in PHP).
wp config set WP_DEBUG true --raw

# Check whether a constant is truthy (exit code 0 = true).
# Added in WP-CLI 2.9.0. Useful in shell conditionals.
wp config is-true WP_DEBUG

# List all defined constants, variables, and file includes.
wp config list

For a deeper dive into what each constant does and which defaults matter, the wp-config.php reference covers the full set.

Source: wp config commands.

Cron management

WP-Cron is a pseudo-scheduler that fires only when someone visits the site. On low-traffic sites or sites behind aggressive page caching, scheduled events silently stop running. The reliable fix is to disable WP-Cron and replace it with a system cron job.

# List all scheduled cron events with their next run time.
wp cron event list

# Manually trigger a specific hook.
wp cron event run wp_update_plugins

# Run all events that are currently due.
wp cron event run --due-now

# List defined cron schedules (hourly, twicedaily, daily, etc.).
wp cron schedule list

# Test whether WP-Cron's HTTP loopback works.
wp cron test

Replacing WP-Cron with a system cron

First, disable the built-in WP-Cron trigger in wp-config.php:

define('DISABLE_WP_CRON', true);

Then add a line to your server's crontab:

# Runs every minute. WP-CLI executes only the events that are actually due.
* * * * * /usr/local/bin/wp --path=/var/www/html cron event run --due-now >/dev/null 2>&1

The --due-now flag is the key: it checks which events have passed their scheduled time and runs only those. Running wp cron event run --due-now every minute is safe and idempotent.

WP-CLI cron runs in a CLI process, not via HTTP. This is a separate execution path from WP-Cron's internal HTTP POST to wp-cron.php. PHP errors surface in the terminal (or crontab mail) instead of being swallowed by a background HTTP request.

Source: wp cron commands.

Cache and transients

# Flush the object cache.
wp cache flush

# Report which cache backend is active (default, Redis, Memcached, etc.).
wp cache type

# Delete all transients from the database.
wp transient delete --all

When wp cache flush does nothing. Without a persistent cache drop-in (Redis, Memcached), WordPress's object cache lives in PHP memory for the current process only. Running wp cache flush clears the CLI process's in-memory cache, which is discarded when the command exits anyway. In that scenario, wp cache flush is a no-op. It only has a real effect when a persistent cache drop-in is installed, in which case it flushes the shared backend and affects all requests site-wide.

In WP-CLI 2.12.0, both wp cache and wp transient gained pluck and patch subcommands for reading and modifying nested values inside cached arrays without fetching and re-setting the entire blob.

Source: wp cache commands, wp transient commands.

Health checks with wp doctor

wp doctor is not bundled with WP-CLI. Install it as a package first:

wp package install wp-cli/doctor-command

Then run it:

# Run all default diagnostic checks.
wp doctor check --all

The default check suite covers 13 items:

Check What it flags
autoload-options-size Autoloaded options exceed 900 KB (a common WordPress performance problem).
constant-savequeries-falsy SAVEQUERIES is true in production (memory leak).
constant-wp-debug-falsy WP_DEBUG is true in production (error output exposed).
core-update WordPress is out of date.
core-verify-checksums Core files have been modified.
cron-count More than 50 cron events queued.
cron-duplicates More than 10 duplicate cron events.
file-eval Suspicious eval() patterns in theme/plugin files.
option-blog-public blog_public discourages search engines.
plugin-active-count 80+ plugins active.
plugin-deactivated 40%+ of plugins are deactivated (cleanup opportunity).
plugin-update Pending plugin updates.
theme-update Pending theme updates.

Each check returns success, warning, or error. Custom check suites can be defined in a doctor.yml file.

Source: WP-CLI Doctor handbook, doctor-command on GitHub.

Running WP-CLI in Docker

Docker containers typically run as root. WP-CLI refuses to run as root by default and prints a security warning: "any code within your WordPress instance will have full privileges to the entire server." Two ways to suppress it:

Option 1: the --allow-root flag per command.

docker exec wordpress-app wp --allow-root plugin update --all

Option 2: the WP_CLI_ALLOW_ROOT environment variable (cleaner for Docker).

Set it in your compose.yaml or Dockerfile so every wp invocation inside the container skips the warning automatically:

# compose.yaml snippet
services:
  wpcli:
    image: wordpress:cli
    environment:
      WP_CLI_ALLOW_ROOT: "1"
    volumes:
      - wordpress-data:/var/www/html

The --allow-root flag does not grant additional privileges. It only suppresses the warning. The WordPress Docker tutorial covers a full Compose setup with WP-CLI as a profiled service.

Running as a non-root user is always preferable. In Docker, this means adding a user: directive in the Compose file or a USER instruction in the Dockerfile. The container sandbox is not a security boundary in every orchestration setup, and running as root inside the container becomes a real privilege escalation vector if the container can reach the host.

Source: WP-CLI GitHub issue #4548.

Useful aliases and shell scripts

Weekly maintenance script

#!/usr/bin/env bash
# wp-maintenance.sh - run weekly via cron or manually.
set -euo pipefail

WP_PATH="/var/www/html"

echo "=== Core integrity check ==="
wp --path="$WP_PATH" core verify-checksums

echo "=== Plugin updates ==="
wp --path="$WP_PATH" plugin update --all

echo "=== Theme updates ==="
wp --path="$WP_PATH" theme update --all

echo "=== Database optimization ==="
wp --path="$WP_PATH" db optimize

echo "=== Cache flush ==="
wp --path="$WP_PATH" cache flush

echo "=== Doctor check ==="
wp --path="$WP_PATH" doctor check --all

Shell aliases

Add these to ~/.bashrc or ~/.zshrc:

# Quick site info.
alias wpinfo='wp --info && wp core version && wp plugin list --format=table'

# Safe search-replace (dry-run only).
alias wpsr-dry='wp search-replace --all-tables-with-prefix --skip-columns=guid --dry-run'

# Export with datestamp.
alias wpbackup='wp db export backup-$(date +%Y%m%d-%H%M%S).sql'

Listing plugins with pending updates as JSON

wp plugin list --update=available --format=json | jq '.[].name'

This is useful for feeding into monitoring or notification systems. The --format=json flag works on virtually every wp ... list command.

Common misconceptions

"WP-CLI requires SSH access." It does not. WP-CLI is a PHP command-line tool that runs in any shell session: an SSH terminal, a docker exec shell, a vagrant ssh session, a kubectl exec pod, or a hosting panel's built-in terminal. Many managed WordPress hosts (Kinsta, WP Engine, Pantheon) offer WP-CLI through their control panel without the user configuring SSH at all. WP-CLI's --ssh flag lets you proxy commands to a remote server over SSH, but local usage needs no SSH whatsoever.

"wp search-replace without --precise is fine for most sites." It is not. Without --precise, WP-CLI uses SQL-level string replacement and only switches to PHP processing when it detects serialised data through column-type heuristics. Those heuristics can miss serialised blobs in non-standard columns, silently corrupting them. The --precise flag forces PHP-level processing for every row. It is slower, but on databases under a few gigabytes the difference is negligible. Always use --precise for migrations.

"WP-CLI's cron commands use the same path as WP-Cron." They do not. WP-Cron works by spawning an HTTP POST to wp-cron.php on every page load. wp cron event run executes events directly in the CLI process, with no HTTP request involved. This is a completely separate execution path. PHP fatal errors that WP-Cron swallows silently appear in the terminal when you run wp cron event run. This separation is exactly why the system cron replacement pattern is more reliable.

Recurring server or deployment issues?

I help teams make production reliable with CI/CD, Kubernetes, and cloud—so fixes stick and deploys stop being stressful.

Explore DevOps consultancy

Search this site

Start typing to search, or browse the knowledge base and blog.