WP-CLI is the Command Line Interface for WordPress. It is used to programmatically manage WordPress websites (from the terminal). It is not a part of core WordPress. It is an external open source project, although developed by many companies that work on WordPress, such as Automattic. Its goal is to be the CLI wp-admin. Everything you can do in /wp-admin/, you can do with WP-CLI (and much more). Moreover, it is faster, more robust, and can be automated (e.g., in CI/CD pipelines).
WP-CLI is large. It has hundreds of commands and options. You could probably use it to manage your entire WordPress website without ever visiting /wp-admin/ in your browser. As such, it would be impossible for me to cover it in full. This would be an undertaking for a whole new guide, and I’m sure you already can’t wait to finish this one (so can’t I). Check out the official WP-CLI Handbook to learn more.
There are many ways you can install WP-CLI, but there are two primary ones. The most common is downloading the wp-cli.phar file, making it executable, and moving it to your PATH (with the name “wp”). When you do that, you can just use “wp {command}” anywhere on your system. The other one is using Composer. In such a case, WP-CLI is bundled with your project. This is not a tutorial so I won’t show you how to do it. Go read the docs.
Commands
I feel like the most reasonable way to begin this chapter is by discussing a few useful commands. We can start with a simple example:
wp --infoThis will show you some relevant system information, including the PHP version, location of the used php.ini file, OS version, WP-CLI version, etc. As I said, there are a lot of commands in WP-CLI. I will only list a few I think are the most useful, but I won’t really explain them. You can find all that info in the docs. The WP-CLI Commands list contains detailed documentation of every command. Always look there when using WP-CLI.
wp cache – manage Object Cache:
- wp cache add
- wp cache delete
- wp cache flush
- wp cache get
- wp cache set
wp cap – manage capabilities of user roles:
- wp cap add
- wp cap list
- wp cap remove
wp comment – manage comments:
- wp comment approve
- wp comment create
- wp comment delete
- wp comment get
- wp comment list
wp config – manage and read wp-config.php:
- wp config create
- wp config edit
- wp config get – gets the value of a specific constant or variable.
- wp config set – sets the value of a specific constant or variable.
wp core – manage WordPress core:
- wp core install
- wp core multisite-install
- wp core update
wp cron – manage WP-Cron:
- wp cron event delete
- wp cron event list
- wp cron event run – you can use “wp cron event run –due-now” in a system cron job to make cron reliable.
- wp cron event schedule
wp db – manage the database:
- wp db create
- wp db drop
- wp db export
- wp db query
- wp db tables
wp eval – execute arbitrary PHP code. For example: wp eval ‘echo WP_CONTENT_DIR;’
wp eval-file – load and execute a PHP file. For example, wp eval-file ./path/to/wp-script.php
wp i18n – manage internationalization:
- wp i18n make-json
- wp i18n make-mo
- wp i18n make-php
- wp i18n make-pot
- wp i18n update-po
wp media – manage media files:
- wp media import
- wp media regenerate
wp menu – manage navigation menus (classic themes):
- wp menu create
- wp menu delete
- wp menu item add-custom
- wp menu item add-post
- wp menu location assign
wp option – manage options:
- wp option add
- wp option delete
- wp option get
- wp option update
wp plugin – manage plugins:
- wp plugin activate
- wp plugin deactivate
- wp plugin install
- wp plugin uninstall
wp post – manage posts:
- wp post create
- wp post delete
- wp post edit
- wp post meta add
- wp post meta delete
wp post-type – retrieve information on registered post types:
- wp post-type get
- wp post-type list
wp rewrite – interact with the Rewrite API:
- wp rewrite flush
wp role – manage user roles:
- wp role create
- wp role delete
wp scaffold – generate file structure and code for different things (this one is cool af):
- wp scaffold block
- wp scaffold child-theme
- wp scaffold plugin
- wp scaffold post-type
- wp scaffold taxonomy
wp search-replace – search and replace strings in the database (serialization-aware).
wp site – manage sites on Multisite:
- wp site activate
- wp site create
- wp site deactivate
- wp site delete
wp super-admin – manage super admin users on Multisite:
- wp super-admin add
- wp super-admin list
- wp super-admin remove
wp taxonomy – retrieve information on registered taxonomies:
- wp taxonomy get
- wp taxonomy list
wp term – manage taxonomy terms and term meta:
- wp term create
- wp term delete
- wp term update
- wp term meta add
- wp term meta delete
wp theme – manage themes:
- wp theme activate
- wp theme delete
- wp theme install
- wp theme update
wp transient – manage transients:
- wp transient delete
- wp transient get
- wp transient set
wp user – manage users:
- wp user create
- wp user delete
- wp user get
- wp user update
wp widget – manage widgets:
- wp widget add
- wp widget deactivate
- wp widget delete
- wp widget move
That’s a lot of commands, isn’t it? Well, it’s probably about 20-30% of all core WP-CLI commands, and I haven’t even listed any options you can use to configure their behavior. That’s what I meant when I said it was a large topic. I chose the ones above just to show you roughly what’s possible with WP-CLI. Again – read the docs when actually using them.
PS: Many commands support the –format option, allowing you to format the response as JSON or something similar. This facilitates scripting.
How WP-CLI Loads WordPress
There are two types of WP-CLI commands – the ones that require WordPress, and the ones that don’t. I’m not really going to dive deep into the internals of WP-CLI. Go read the source if you’re into that kind of stuff. The one thing you need to understand is that the method managing WP-CLI execution tries to execute the command if it’s intended to be processed before WordPress loads. If the command requires WordPress, it proceeds to bootstrap it first.
The way WP-CLI loads WordPress is pretty interesting. It doesn’t just make a request to your website – that would be inefficient and stupid. Instead, it tries to locate the wp-config.php file and, if found, loads its code into memory. Then it removes the line that requires wp-settings.php and executes wp-config.php using eval(). This step is necessary so that all wp-config.php constants get defined (like db credentials), but the bootstrap process doesn’t proceed on its own.
Then it does a few other internal operations, and finally after a moment – it loads wp-settings.php. Almost the entirety of WordPress is loaded in just this file (which you should already know). Some important global variables like $wpdb are instantiated, and plugins are loaded. That’s right, every WP-CLI command that loads WordPress also loads all plugins and calls the ‘init’ hook. This has some important consequences we’ll talk about in the “Making Plugins WP-CLI Compatible” section.
That’s where similarities with the normal request lifecycle end. You can see that the only steps shared are step 4 and 5. The code parsing the request doesn’t run, so the global WP_Query isn’t instantiated. Similarly, the template loader (responsible for finding the template to render) doesn’t run either, which shouldn’t really be a surprise. The command executes in this WordPress environment (run after wp-settings.php) and the process finishes.
Note that all of this happens on the system that the website is running on. You can’t just run a WP-CLI command on any remote WordPress website. This means that you must have WP-CLI installed on your server for it to work. There is a way to use it over SSH, but it still needs to be installed on the server.
Custom Commands
The built-in commands are enough to do everything you can do in the native wp-admin. But the wp-admin isn’t only the native stuff. It’s heavily extended by pretty much all plugins. What if you made a backup plugin that allowed users to take full backups of their site with a click of a button? That’s an action in wp-admin. If your plugin is big enough (i.e., professional), you might think about creating a custom WP-CLI command for that.
I’m only going to scratch the surface of creating commands. Go read the docs when actually doing it. A command is basically just a name (the command you write after “wp”) mapped to a callback, like a function, class, or closure. Commands can be bundled with plugins or they can be external packages. These packages are installed and managed on the system with “wp package”. Packages to WP-CLI are like plugins to WordPress.
Probably the most robust method for callback definition is with a class. That’s because every public method of the class automatically becomes a subcommand. This is useful if you need many subcommands. Command definitions are pretty verbose because PHPDoc is used to create help for the command. Here is an example of a simple command:
<?php
if ( defined( 'WP_CLI' ) && WP_CLI ) {
class My_Command {
/**
* Prints a greeting.
*
* ## OPTIONS
*
* <name>
* : The name to greet.
*
* [--yell]
* : If set, the greeting will be uppercase.
*
* ## EXAMPLES
*
* wp mycmd greet world --yell
*
* @when after_wp_load
*/
public function greet( $args, $assoc_args ) {
list( $name ) = $args;
$greeting = "Hello, $name";
if ( isset( $assoc_args['yell'] ) ) {
$greeting = strtoupper( $greeting );
}
WP_CLI::success( $greeting );
}
}
WP_CLI::add_command( 'mycmd', 'My_Command' );
}This command is defined in WordPress (e.g., in a plugin). You can tell because of the check for the WP_CLI constant. This constant is only true if WordPress is loaded in the context of WP-CLI. You don’t have to hook that code to any action. It can just run when your plugin is loaded in wp-settings.php. An example usage could be:
wp mycmd greet Wiktor --yell
> HELLO, WIKTORYou can see that the greet() method was automatically registered as a subcommand to “mycmd”. Also, notice the “@when after_wp_load” at the bottom of the PHPDoc comment. It tells WP-CLI when in the bootstrap process to execute the callback. “after_wp_load” is actually the name of a hook (not a WordPress hook – WP-CLI hook). WP-CLI uses a very similar hook system to WordPress.
“after_wp_load” is the default hook, so it’s redundant to specify it. The most important hook you can use here is “before_wp_load”. This will execute the command before the WordPress bootstrap process. That’s of course only in theory. Keep in mind that this command is added in a WordPress plugin. This means that for WP-CLI to even be aware of it, the WordPress must’ve already been loaded. In reality, execution before WP loads can only be achieved if you make your command a package (as opposed to bundling it with a plugin).
Making Plugins WP-CLI Compatible
As is the case with many other things, the most important part of making your plugin WP-CLI compatible is knowing you should care about it in the first place. Sometimes your plugins should behave differently when WordPress is loaded in the context of WP-CLI. One such example could be a plugin that counts the number of visitors. You should know how your plugin’s logic should change when WordPress is loaded by WP-CLI. Check for the “WP_CLI” constant to do something conditionally (or run your code on actions that WP-CLI doesn’t reach).
The biggest difference between a normal bootstrap and WP-CLI bootstrap is the fact that the context is not an HTTP request. This means that HTTP-specific superglobals, like $_SERVER, $_GET, $_POST, etc., might not be populated with values you’d expect in a normal request. If your plugin has any code that runs during WP-CLI (e.g., on init) that blindly uses these values without checking if they exist, you might encounter warnings or errors. The same goes for other HTTP-specific code (like sending headers).
Last but not least, don’t echo, print, or die when WP-CLI is running. Printing text directly may interfere with the command output and make it unreadable (i.e., when –format=json is used). If you have to print something, you can use WP_CLI::log() or WP_CLI::error() when you detect the WP-CLI context.
And don’t forget to test your plugin with WP-CLI!
Useful Packages
This section is very pragmatic and specific, but I want to give you a broad idea of what’s possible with external WP-CLI packages by showing you 3 life-changing ones.
wp doctor helps you diagnose problems on WordPress installations. It runs a series of configurable checks. For example, you can check if:
- the number of cron jobs is within normal range,
- the size of autoloaded options doesn’t exceed a specified threshold,
- the WP_DEBUG constant is set to false,
- core needs an update,
- the number of plugins is too large,
- the core WordPress files have been tampered with,
- and much more…
And these are only the built-in checks. The doctor package allows you to write custom doctor.yml configuration files with checks stitched together from a predefined list of options, or you can write completely custom checks with PHP. For example, you can check if a given option in the database has the value you expect, or if a particular plugin is active. Best part? You can specify –format=json and pipe it to a custom script. Run a cron job like that once a day and notify yourself when asserts fail.
wp profile lets you profile WordPress execution. By “profiling” I mean measuring things like execution time (probably the most important feature). You can run “wp profile stage” to see how long it takes for each stage of WordPress to execute. You can also run “wp profile hook” to see how long it takes for each individual hook to execute and which callbacks are taking what amount of time. If you’ve ever had to deal with a slow WordPress website and you just learned about it, your jaw should be on the floor (mine sure was). Just look at that (–spotlight filters out zeroish values):

wp faker helps create dummy data for testing purposes. It automatically generates things like posts, pages, authors, attachments, categories, and tags, but also WooCommerce products, brands, reviews, and more. You can use it to get realistic content quickly. It’s useful in test/dev environments, where you can see how your theme looks on actual data without having to manually input it.