WP-CLI Cheatsheet
Last updated 21st May 2026 Reviewed May 2026

WP-CLI Cheatsheet

What it is

WP-CLI is the command-line interface for WordPress — every action you can perform in the wp-admin dashboard (install plugins, update core, edit options, run a multisite network) has a wp subcommand that does the same thing without a browser. For deployment workflows that means treating WordPress like any other PHP app: install plugins from composer.json, swap URLs after a database restore, flush caches in a post-deploy hook, and never SSH into a host to "just fix this one option" again.

This sheet covers the commands and flags you reach for when wiring WordPress into a CI/CD pipeline — the search-replace and db export patterns that survive a staging-to-production migration, the plugin/theme update flows that don't require a browser session, and the cache discipline that keeps a multi-environment WordPress fleet predictable.

Quick reference

Installing WP-CLI and sanity checks

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                   # global install

wp --info                                               # PHP version, MySQL, WP root
wp cli version                                          # WP-CLI version
wp cli update                                           # self-update
wp cli check-update                                     # check without installing
wp doctor list                                          # diagnostics (needs wp-cli/doctor-command)

Useful environment defaults — set once per server or per CI step:

export WP_CLI_PHP=/usr/bin/php8.2                       # pin a specific PHP binary
export WP_CLI_PHP_ARGS="-d memory_limit=512M"           # increase memory for bulk ops
export WP_CLI_ALLOW_ROOT=1                              # CI runs as root — opt in explicitly

Site bootstrap

wp core download                                        # download WP into the cwd
wp core download --version=6.5.4 --locale=en_GB
wp config create \
  --dbname=app --dbuser=app --dbpass=secret --dbhost=db --dbprefix=wp_

wp core install \
  --url=example.com --title="My Site" \
  --admin_user=admin --admin_email=team@example.com --admin_password=PASSWORD --skip-email

wp core is-installed                                    # exit 0 if installed (CI-friendly)
wp core verify-checksums                                # detect tampered core files

Users

wp user list                                            # interactive table
wp user list --role=administrator --field=user_email --format=csv
wp user create alice alice@example.com --role=editor --user_pass=PASSWORD --send-email=false
wp user update 42 --user_pass="$NEW_PASSWORD"
wp user delete 42 --reassign=1                          # reassign content to user 1
wp user one-time-login alice                            # one-shot magic link (debugging only)

Plugins

wp plugin list                                          # name, status, version, update
wp plugin list --status=active --field=name
wp plugin install wp-super-cache --activate
wp plugin install https://example.com/plugin.zip --activate
wp plugin install /path/to/plugin.zip --activate        # from local file
wp plugin update --all --quiet
wp plugin update wp-super-cache --version=1.13.0
wp plugin activate wp-super-cache
wp plugin deactivate wp-super-cache
wp plugin delete wp-super-cache
wp plugin is-installed wp-super-cache                   # exit 0 if installed
wp plugin path wp-super-cache                           # absolute path

Themes

wp theme list
wp theme install twentytwentyfour --activate
wp theme install /path/to/theme.zip --activate
wp theme update --all
wp theme activate my-child-theme
wp theme delete inactive-theme
wp theme mod list                                       # current customizer settings
wp theme mod set background_color FFFFFF

Posts, pages, and taxonomies

wp post list --post_type=page --field=ID
wp post list --post_status=publish --posts_per_page=20 --format=table

wp post create --post_type=post --post_title="Release notes" --post_status=publish \
  --post_content="$(cat changelog.md)"

wp post update 42 --post_status=draft
wp post delete 42 --force                               # bypass trash
wp post meta set 42 _custom_field "value"

wp term list category --field=slug
wp term create category "Releases" --slug=releases --description="Release notes"

Options, transients, and rewrite rules

wp option get siteurl
wp option get blogname
wp option update blogname "Production"
wp option update siteurl "https://example.com"
wp option update home    "https://example.com"
wp option pluck rewrite_rules                           # dump nested option values

wp transient delete --all                               # nuke all transients
wp transient delete some_key

wp rewrite flush                                        # rebuild permalink rules
wp rewrite structure '/%postname%/'                     # set permalink structure

Database

wp db check                                             # mysqlcheck
wp db optimize
wp db repair
wp db size --tables
wp db tables                                            # list with prefix
wp db query "SELECT COUNT(*) FROM wp_posts WHERE post_status='publish';"

wp db export backup.sql                                 # mysqldump → file
wp db export - | gzip > backup.sql.gz                   # streamed/compressed
wp db export --tables=wp_posts,wp_postmeta posts.sql

wp db import backup.sql
wp db reset --yes                                       # DROP + CREATE all tables

search-replace (the one you'll use most)

wp search-replace 'http://staging.example.com' 'https://example.com'
wp search-replace 'old.example.com' 'new.example.com' --dry-run
wp search-replace 'old' 'new' --all-tables-with-prefix
wp search-replace 'old' 'new' --skip-columns=guid       # NEVER touch guid (it's an identifier)
wp search-replace 'old' 'new' --precise                 # slower but correct for serialized PHP
wp search-replace 'old' 'new' --export=migrated.sql     # dump replacement, don't write DB

wp search-replace handles serialized PHP correctly — a plain sed on a mysqldump corrupts every serialized array length prefix and breaks the site. Always use this for URL migrations.

Cache and OPcache

wp cache flush                                          # object cache (Redis/Memcached)
wp cache flush-group some_group                         # WP 6.x+
wp transient delete --all
wp rewrite flush

wp eval 'if (function_exists("opcache_reset")) opcache_reset();'   # PHP OPcache
wp eval 'echo wp_get_environment_type();'               # production/staging/development

Multisite

wp site list --field=url
wp site create --slug=blog --title="Marketing" --email=team@example.com
wp site delete 3 --yes
wp site activate 3
wp site deactivate 3

wp site option update 3 blogname "Marketing"
wp super-admin list
wp super-admin add admin

# Run a command across every subsite
for url in $(wp site list --field=url); do
  wp --url="$url" plugin update --all
done

Cron events

wp cron event list
wp cron event run --due-now
wp cron event run my_custom_hook
wp cron event delete my_custom_hook
wp cron test                                            # verify wp-cron reachability

For production: disable DISABLE_WP_CRON in wp-config.php and call wp cron event run --due-now from a real cron job — see the Cron and Crontab cheatsheet for the schedule pattern.

REST API and roles

wp rest list --format=table                             # endpoints + capabilities
wp role list
wp role create editor_lite "Editor Lite" --clone=editor
wp cap list editor_lite
wp cap add editor_lite manage_options

Deployment workflows (the moat)

1. URL migrations after a staging-to-production restore

Restoring a staging database into production is a common deploy pattern (or recovery pattern after a botched release) — and every internal URL needs swapping before the site is usable. Don't do this with sed; serialised PHP corrupts.

#!/usr/bin/env bash
set -euo pipefail

# 1. Pull the staging dump
ssh staging "wp db export - --add-drop-table" | gunzip > /tmp/staging.sql

# 2. Restore on production (after a backup — see workflow 2)
wp db reset --yes
wp db import /tmp/staging.sql

# 3. Swap URLs — dry-run first
wp search-replace 'https://staging.example.com' 'https://example.com' \
  --all-tables-with-prefix --skip-columns=guid --dry-run

# 4. Real swap if dry-run looked sane
wp search-replace 'https://staging.example.com' 'https://example.com' \
  --all-tables-with-prefix --skip-columns=guid

# 5. Flush caches
wp cache flush
wp rewrite flush
wp transient delete --all

Why --skip-columns=guid: the guid column is meant to be a permanent identifier for each post — RSS readers and external systems use it as a dedupe key. Rewriting it breaks subscriptions in ways that take weeks to notice.

Why --all-tables-with-prefix: plugins routinely serialise URLs into wp_options AND into their own tables (wp_yoast_seo_links, wp_woocommerce_*). The default wp search-replace only walks the core tables — --all-tables-with-prefix walks every table with the configured prefix, which is what you actually want.

2. Pre-deploy database export as an atomic gate

Before any deploy that touches schema or runs a migration, take a snapshot — and make sure it actually wrote:

#!/usr/bin/env bash
set -euo pipefail

BACKUP="/var/backups/wp/$(date -u +%Y%m%dT%H%M%SZ).sql.gz"
mkdir -p "$(dirname "$BACKUP")"

# Export streamed through gzip — no temporary uncompressed dump on disk
wp db export - --add-drop-table | gzip -9 > "$BACKUP"

# Verify the dump is non-empty AND contains expected DDL
test -s "$BACKUP" || { echo "backup is empty" >&2; exit 1; }
gunzip -c "$BACKUP" | grep -q 'CREATE TABLE.*wp_options' \
  || { echo "backup missing wp_options DDL" >&2; exit 1; }

# Keep last 7 days
find /var/backups/wp -name '*.sql.gz' -mtime +7 -delete

echo "backup verified: $BACKUP"

The grep step catches the failure mode that bites everyone eventually: wp db export succeeds (exit 0) but produces a tiny dump because the DB credentials reference an empty database. A test -s alone doesn't catch that. Confirming a known table appears in the dump does.

For DeployHQ deploys, wire this as a pre-deploy SSH command — failing here aborts the deploy before any code changes, so production is never sitting on an "almost deployed" half-state.

3. Plugin and theme updates from CI

WordPress sites in production should not be running "Update available" prompts. Either updates flow through Composer (preferred — the vendor/ directory is the source of truth), or through WP-CLI in a deploy step. Either way, never click "Update" in wp-admin.

#!/usr/bin/env bash
set -euo pipefail
export WP_CLI_ALLOW_ROOT=1

# Snapshot DB before bulk updates — see workflow 2
/usr/local/bin/pre-deploy-backup.sh

# Core
wp core update --version=6.5.4 --quiet
wp core update-db                                       # idempotent, safe to re-run

# All plugins, exclude maintenance-mode-sensitive ones
wp plugin update --all --exclude=wp-super-cache,wordfence --quiet

# Specific plugin to a pinned version
wp plugin update yoast-seo --version=22.4 --quiet

# Themes
wp theme update --all --quiet

# Cleanup
wp cache flush
wp rewrite flush
wp transient delete --all

# Smoke-test the homepage post-update
curl -fsS -o /dev/null -w "%{http_code}\n" https://example.com/ | grep -q '^200$' \
  || { echo "homepage non-200 after update" >&2; exit 1; }

The smoke-test step matters: a plugin update can put the site into a fatal error state where wp itself still works but every page returns 500. Curling the homepage from inside the deploy script catches that before traffic gets routed back.

4. Multisite deploys without forgetting a subsite

In a multisite network, every command needs the right --url flag — and "I'll run it on the main site, it should propagate" is the classic source of half-migrated networks. Pattern that holds:

#!/usr/bin/env bash
set -euo pipefail

# Network-wide plugin install (NOT per-subsite)
wp plugin install advanced-custom-fields-pro --activate-network

# Per-subsite operations — iterate explicitly
mapfile -t URLS < <(wp site list --field=url)

for url in "${URLS[@]}"; do
  echo "::group::$url"
  wp --url="$url" plugin update --all --quiet
  wp --url="$url" cache flush
  wp --url="$url" rewrite flush
  echo "::endgroup::"
done

--activate-network is the multisite-aware activation flag — it activates the plugin for every subsite AND prevents per-subsite activation. Forgetting it on a security-related plugin (Wordfence, iThemes Security) is the kind of mistake that takes a postmortem to find.

The mapfile pattern captures all URLs into a Bash array before the loop runs — so if one subsite's update kills the wp-cron daemon mid-loop, you still have the list of remaining subsites to retry.

5. Cache and OPcache flush as the last deploy step

A WordPress deploy that doesn't flush all four cache layers ends up serving stale content from somewhere. The four layers, in flush order:

# 1. Object cache (Redis/Memcached) — invalidates wp_options, transients
wp cache flush

# 2. Page cache (whichever plugin is installed)
wp super-cache flush 2>/dev/null || true                # wp-super-cache
wp cache-enabler clear 2>/dev/null || true              # cache-enabler
wp rocket clean --confirm 2>/dev/null || true           # wp-rocket
wp w3-total-cache flush all 2>/dev/null || true         # w3-total-cache

# 3. Permalink rewrite rules (always last among DB caches)
wp rewrite flush --hard

# 4. PHP OPcache — files on disk changed but OPcache still has the old bytecode
wp eval 'if (function_exists("opcache_reset")) opcache_reset();'
# Or, if PHP-FPM:
sudo systemctl reload php8.2-fpm

# 5. CDN / edge cache (Cloudflare, BunnyCDN, etc.) — outside WP, fire via curl/API

The 2>/dev/null || true pattern lets the script run on any host without knowing in advance which cache plugin is active — it tries each and ignores the ones that aren't installed. That's slightly less explicit than checking with wp plugin is-installed first, but the script stays portable across staging and production when they happen to run different cache plugins.

For an atomic deploy that uses zero-downtime deployments, OPcache flush is the most important step — without it the new release directory is live (the symlink swapped) but PHP-FPM is still executing the old bytecode it cached against the previous release path. Reload php-fpm or call opcache_reset() from the post-deploy hook.

6. Composer-managed WordPress sites

For sites where Composer is the source of truth (Roots/Bedrock, custom setups), WP-CLI's role narrows to runtime tasks — DB ops, search-replace, cron, cache flush — while plugin/theme installs flow through composer install. The deploy script looks like:

#!/usr/bin/env bash
set -euo pipefail

# Pull code + install vendor/ on the BUILD host (not production)
composer install --no-dev --prefer-dist --optimize-autoloader --classmap-authoritative

# On the production host, after symlink swap:
wp core update-db --quiet                               # safe if no DB changes
wp cache flush
wp rewrite flush --hard
wp eval 'opcache_reset();'

See the Composer cheatsheet for the production install flags and the composer.lock discipline that makes this work.


Common errors and fixes

Error / symptom Cause Fix
Error: This does not seem to be a WordPress installation Running wp outside the WP root cd to WP root or use --path=/var/www/current
Error establishing a database connection from WP-CLI but wp-admin works wp-config.php references localhost but DB is on a Unix socket Add --dbhost=localhost:/var/run/mysqld/mysqld.sock to config
Error: YIKES! It looks like you're running this as root Default safety guard Set WP_CLI_ALLOW_ROOT=1 in CI, or use sudo -u www-data wp ...
Error: Couldn't connect to localhost:80 (network plugins) Plugin tries to call its own REST API during activation Run with --skip-plugins=<offender> for activation, then re-enable
search-replace runs forever on a large site Each row touched even if nothing matched Add --include-columns=post_content,post_excerpt to scope
search-replace mangles serialized data --precise not set Always add --precise for serialised data; default scan misses some edge cases
Fatal error: Allowed memory size exhausted PHP CLI memory limit too low WP_CLI_PHP_ARGS="-d memory_limit=512M" wp ...
wp cron event run never fires events DISABLE_WP_CRON true but no system cron set Add wp cron event run --due-now to system crontab (see Cron cheatsheet)
Multisite: wp option get returns site #1's value Missing --url flag — defaults to main site Add --url=https://blog.example.com/
Plugin update failed: download failed Outbound HTTPS blocked or DNS broken Test curl -fsS https://downloads.wordpress.org/; whitelist on firewall
Site shows old content after deploy OPcache not flushed wp eval 'opcache_reset();' or systemctl reload php-fpm
wp command not found in cron Cron's PATH is minimal Use full path /usr/local/bin/wp in cron lines

Companion: full DeployHQ deploy workflow

A WordPress deploy is the same atomic-release pattern as any PHP app, with one extra concern: the database typically lives outside the release directory, so schema changes (plugin updates, core upgrades) are durable — you can't roll them back by swapping the symlink. That's what makes the pre-deploy wp db export (workflow 2) the load-bearing step in this whole sheet.

For the end-to-end pattern — GitHub push → CI build (composer install) → DeployHQ webhook → SSH deploy hooks (the wp-cli steps above) → smoke test → symlink swap — wire it through DeployHQ's build pipelines and the deploy from GitHub guide. If a release does ship a bad plugin update, one-click rollback reverts the file tree while you wp db import the pre-deploy backup.

Start a free DeployHQ trial to wire wp-cli into a production deploy pipeline in minutes.


  • Composer cheatsheet — for the composer install --no-dev step that ships your plugins and themes.
  • Cron and Crontab cheatsheet — for replacing WordPress's traffic-driven wp-cron with a real system cron.
  • Bash cheatsheet — for the set -euo pipefail scripts that orchestrate the WP-CLI steps above.
  • SSH cheatsheet — for the deploy keys and ~/.ssh/config patterns the deploy script runs over.
  • Docker cheatsheet — for the container patterns when WP-CLI runs inside a containerised PHP stack.
  • Cheatsheets hub — every DeployHQ cheatsheet in one place.

Need help? Email support@deployhq.com or follow @deployhq on X.