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
wptoo, not just the website, becausewploads 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.phpconstants. If you setWP_DEBUGthere, 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
| Piece | Value | What 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
.
| Command | Manages |
|---|---|
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.
| Flag | What 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.