Skip to main content
Blog|
Learning center

WP-CLI: a complete guide for WordPress developers

|
Aug 25, 2026|13 min read
LEARNING CENTERWP-CLI: a complete guide forWordPress developersHOSTNEYhostney.comAugust 25, 2026

WP-CLI is the official command-line interface for WordPress. It does everything the admin dashboard does, plus a long list of things the dashboard cannot do at all, and it does it in a form you can script, schedule, and run against fifty sites in a loop.

Most WP-CLI articles are recipe lists: here are twenty commands, copy them. That is useful once. What actually makes WP-CLI fast is understanding the grammar behind it, because once you know how a command is put together you can guess commands you have never seen and be right most of the time. This guide covers the structure: how commands are built, the global flags that work on every one of them, how to get machine-readable output for scripts, how config files and aliases let you run against staging and production without retyping paths, and the safety habits that keep a one-line command from taking down a live site.

If you need the install steps and a starter command list, how to install WordPress with WP-CLI covers getting the phar onto a server, the four-command WordPress install, and the everyday plugin, theme, user, and database commands. This guide picks up where that one leaves off.

What WP-CLI actually is#

WP-CLI is a PHP program that boots WordPress the same way an HTTP request does. It loads wp-config.php , connects to the database, loads the active plugins and theme, and fires the same hooks. Only then does it run your command.

That single fact explains most of WP-CLI’s behavior:

  • It has to be run from inside a WordPress directory, or told where one is, because it needs wp-config.php .
  • A plugin that throws a fatal error breaks wp too, not just the website, because wp loads that plugin.
  • Commands added by plugins (WooCommerce, Yoast, and many others register their own) appear automatically once the plugin is active.
  • It obeys wp-config.php constants. If you set WP_DEBUG there, WP-CLI is in debug mode too.

WP-CLI needs PHP 7.4 or newer and a shell on the server. It is not something you install into WordPress as a plugin, and it does not run from a browser.

The anatomy of a WP-CLI command#

Every WP-CLI command follows one pattern:

wp <command> <subcommand> [<positional arguments>] [--flags]

Take a real one apart:

wp plugin install wordpress-seo --version=22.0 --activate
PieceValueWhat it is
Command plugin The thing you are working on, always a noun
Subcommand install What you are doing to it, always a verb
Positional argument wordpress-seo Which one, in this case the plugin slug
Associative flag --version=22.0 A named value
Boolean flag --activate On when present, off when absent

The nouns are the useful part to memorize, because the verbs repeat across them. Almost every command family has list , get , create , update , and delete .

CommandManages
wp core WordPress itself: download, install, update, verify checksums
wp plugin / wp theme Plugins and themes
wp user / wp role / wp cap Users, roles, and capabilities
wp post / wp term / wp menu Content, taxonomies, and navigation
wp option / wp transient The options table and cached transients
wp db Direct database access: export, import, query, optimize
wp search-replace Safe find-and-replace across the whole database
wp cron Scheduled events
wp media The media library, including thumbnail regeneration
wp site / wp network Multisite
wp cli WP-CLI itself

Boolean flags can usually be negated with --no- . For example wp plugin install akismet --no-activate is explicit about not activating, which reads better in a script than leaving the flag out.

Finding commands without leaving the terminal#

You do not need to keep documentation open. WP-CLI documents itself.

wp help                      # every top-level command
wp help plugin               # every subcommand of wp plugin
wp plugin install --help     # full flag reference for one subcommand

wp help opens in your pager, so q exits. The per-subcommand help is the one worth reading, because it lists every flag with its accepted values, and those flags are where the power is.

Two more that are worth knowing:

wp cli info          # WP-CLI version, PHP binary, PHP version, config paths
wp cli check-update  # is a newer WP-CLI available

wp cli info is the first thing to run when something behaves oddly, because it tells you which PHP binary WP-CLI picked up and which config files it loaded. Those two answers explain a surprising share of “it works for me but not in cron” problems.

Global flags that work on every command#

These are not per-command flags. They apply to anything you run, and they are the difference between using WP-CLI and using it well.

FlagWhat it does
--path=<dir> Where the WordPress install is, instead of the current directory
--url=<url> Which site to operate on, required on multisite
--user=<id|login|email> Run as a specific WordPress user
--skip-plugins[=<list>] Load WordPress without plugins
--skip-themes[=<list>] Load WordPress without the theme
--require=<file> Load a PHP file before the command runs
--quiet Suppress informational output
--debug Show internal logging and full stack traces
--allow-root Permit running as root
--no-color Strip ANSI colors, useful when piping to a file

-path, for cron and scripts#

WP-CLI looks for WordPress in the current directory and then walks upward. That works interactively because you cd into the site first. It fails in cron, where the working directory is whatever the cron daemon decided, usually the user’s home.

wp --path=/home/user/public_html plugin update --all

Either cd first or pass --path . Never assume a script inherits the directory you tested it from. The same rule applies to scheduled tasks, which is why every example in how to run a WP-CLI command with cron is written with an absolute path.

-url, for multisite and unusual home URLs#

On a multisite network, WordPress cannot know which site you mean, so most commands refuse to run without --url :

wp --url=shop.example.com plugin list
wp site list --field=url          # find the values to pass

It also matters on single sites in one specific case: commands that generate URLs, such as wp rewrite flush or anything that regenerates permalinks, use the site URL from the database. If that value is wrong, --url overrides it for the duration of the command.

-user, because WP-CLI has no user by default#

WP-CLI runs with no logged-in user. For most commands that is irrelevant, but any command that checks capabilities, or any plugin command that calls current_user_can() , will behave differently than it does in the dashboard.

wp --user=1 post create --post_title="Draft" --post_status=draft

Passing --user also sets the post author correctly, which matters when you are creating content in bulk.

-skip-plugins and -skip-themes, the recovery flags#

This is the pair worth remembering, because it solves the worst case. When a plugin update causes a fatal error, the site goes white and wp fails with the same fatal error, because wp loads the plugin too. You cannot deactivate the plugin with the tool you would normally use to deactivate the plugin.

--skip-plugins breaks that loop:

wp --skip-plugins plugin list                    # see what is installed
wp --skip-plugins plugin deactivate broken-plugin

You can also skip selectively, which is better when you need most of the site working:

wp --skip-plugins=broken-plugin,another-one plugin list

The caveat is that a command run with --skip-plugins cannot see anything a plugin registered. Deactivating works because plugin activation state lives in the options table, not in the plugin. But a WooCommerce command will not exist if you skipped WooCommerce. Use it for recovery, not as a habit.

This is the fastest route back into a site that has locked you out of the dashboard, and it is worth trying before anything more invasive. If the site is broken for a reason other than a plugin, how to fix the most common WordPress errors walks the other causes, and how to reset the WordPress admin password covers the case where WP-CLI works but your login does not.

-debug and -quiet#

--debug prints WP-CLI’s own log lines and full stack traces instead of a one-line error. It is the difference between “Error: Something went wrong” and a file and line number.

--quiet is its opposite and belongs in cron entries. Cron mails you anything a job writes to output, so a chatty command produces a message every run and you stop reading them. With --quiet , success is silent and only genuine errors reach you.

Output formats, and using WP-CLI in scripts#

Every list command, and most get commands, accept --format :

wp plugin list --format=table    # default, for humans
wp plugin list --format=csv
wp plugin list --format=json
wp plugin list --format=yaml
wp plugin list --format=ids      # just the identifiers, space separated
wp plugin list --format=count    # just a number

Narrow the columns with --fields for several, or --field for exactly one:

wp plugin list --fields=name,status,version
wp plugin list --field=name

A single --field prints bare values with no header, one per line, which is what you want for a loop. Combined with --format=ids and the standard filter flags, you get real batch operations:

# Deactivate every inactive-but-installed plugin's leftovers
wp plugin list --status=inactive --field=name | xargs -r wp plugin delete

# Every administrator's email address
wp user list --role=administrator --field=user_email

# Count published posts without loading a page
wp post list --post_status=publish --format=count

Two behaviors make WP-CLI safe to script around. Errors go to STDERR, not STDOUT, so $(wp option get home) captures a clean value and never mixes a warning into your variable. And WP-CLI returns a non-zero exit code on failure, so set -e at the top of a bash script stops it at the first failed command instead of charging ahead:

#!/bin/bash
set -euo pipefail
cd /home/user/public_html

wp db export "pre-update-$(date +%F).sql"
wp plugin update --all
wp core update-db

One flag to keep out of scripts: --prompt . It makes WP-CLI ask for each argument interactively, which is helpful when you are learning a command with many parameters and fatal in automation, where it will hang waiting for input that never comes.

Configuration files: wp-cli.yml#

Retyping --path and --url gets old. WP-CLI reads YAML config files and treats their contents as defaults.

It looks for wp-cli.local.yml , then wp-cli.yml , walking up from the current directory, then ~/.wp-cli/config.yml for your user. Put a wp-cli.yml in the project root:

path: public_html
url: https://example.com

core config:
  dbhost: localhost
  extra-php: |
    define('WP_DEBUG', true);

plugin install:
  activate: true

Top-level keys set global flags. A key named after a command sets defaults for that command only, so with the file above wp plugin install akismet activates automatically without the flag.

Commit wp-cli.yml to the repository so the whole team shares it, and keep anything machine-specific or secret in wp-cli.local.yml , which by convention is gitignored. Database credentials belong in wp-config.php , not here. If you are not sure what belongs in which file, wp-config.php explained covers the constants side of the split.

Aliases: one command, any environment#

Aliases are the feature that turns WP-CLI from a server tool into a workflow tool. They are defined in the same YAML file and start with @ :

@production:
  ssh: deploy@example.com/home/deploy/public_html
@staging:
  ssh: deploy@staging.example.com/home/deploy/public_html
@local:
  path: /Users/me/sites/example
@all:
  - @production
  - @staging

The alias goes immediately after wp :

wp @staging plugin list
wp @production db export backup.sql
wp @all core version

With an ssh: key, WP-CLI opens an SSH connection and runs the command on the remote machine. Nothing is transferred to your laptop except the output, which means WP-CLI has to be installed on the remote host as well, and your SSH key has to get you in without a password prompt. If you are still typing a password on every connection, how to set up passwordless SSH login is the prerequisite.

@all runs across every alias in the group, which is how you answer “which of my sites is still on the old plugin version” in one line. Comparing staging against production this way is also the cheapest sanity check before a deploy, and it pairs naturally with a real staging environment rather than a copied folder. How to create a WordPress staging site covers building one properly.

Running WP-CLI over SSH#

You need a shell on the server. That is either your own SSH client, or a terminal built into your host’s control panel.

For a single command, you do not need an interactive session at all:

ssh user@example.com "cd /home/user/public_html && wp plugin list"

Two things trip people up here. Non-interactive SSH commands do not always load the same shell profile as a login session, so wp may not be on the PATH even though it works when you log in normally. Use the full path to the binary if that happens. And quoting matters: the command runs through two shells, so anything with spaces or special characters needs care. How to run commands over SSH covers the quoting rules and chained commands in detail, and what is SSH covers the protocol underneath if you want the background.

Safety habits#

WP-CLI does exactly what you tell it, immediately, with no confirmation dialog and no undo.

Export the database before anything that writes to it. wp db export backup.sql takes seconds and it is the only thing standing between a mistyped search-replace and a rebuild.

Use --dry-run on search-replace . It reports every table and row it would change without touching anything. Read that report before running it for real. search-replace is the command most worth this care, because it correctly rewrites serialized PHP data, which means it is genuinely changing more than a raw SQL update would. That is exactly why it works, and exactly why a wrong search string does so much damage. Why most WordPress migrations fail covers what happens when people reach for a plain find-and-replace instead.

Be deliberate about --allow-root . WordPress expects to run as the web server user. Files created by root are files your web server cannot write to later, and that shows up days afterward as a failed update or a broken upload. Prefer sudo -u www-data wp ... , and reserve --allow-root for containers where root is the only user available.

Test destructive commands against staging first. Aliases make this a one-word change, so there is no excuse.

WP-CLI on Hostney#

On Hostney, WP-CLI is already installed. Open the terminal from the control panel, or connect with your own SSH client, cd into the site directory, and wp is on the path. There is no phar to download and no binary to maintain.

The shell environment also ships Composer, git, and the MySQL client, so a full deploy or maintenance workflow runs in one place. The browser terminal is the fastest route in when you are away from your own machine, and it is documented under terminal access; for an external client, add your key on the SSH keys page first.

Two platform details worth knowing:

The shell has its own PHP build for running WP-CLI, and each website runs on the PHP version you select for it in the control panel. So wp cli info may report a different PHP version than the site itself uses. That is normal and almost never matters, but it is worth knowing before you use WP-CLI to test PHP compatibility. To check what the site is actually running, see how to check your PHP version.

The control panel’s cron scheduler runs URL-based jobs, not shell commands, so scheduled WP-CLI has to be set up differently than the standard crontab approach. The WP-CLI with cron article covers both routes, including replacing wp-cron.php with a scheduled request.

Summary#

WP-CLI has a small grammar and a large vocabulary. Learn the grammar and the vocabulary looks after itself: wp <noun> <verb> <args> --flags , with list , get , create , update , and delete repeating across almost every noun, and wp help <command> filling in the gaps.

The four things that separate casual use from real use are --path so scripts work outside your shell, --skip-plugins so a fatal error cannot lock you out of your own recovery tool, --format and --field so output feeds into other commands, and aliases so one command reaches any environment. Add wp db export before anything destructive and --dry-run on search-replace , and there is very little left that can go badly wrong.