Guides Guides Installing - Recommended and alternative installation mechanisms. Quick start - Where to begin after you've installed WP-CLI for the first time. Running commands remotely - Learn how to remotely control multiple servers at once. Commands cookbook - The full 101 how commands work, writing your own, and sharing them with the world. Writing Behat Tests - How to write and run Behat tests for WP-CLI commands and packages. Common issues and their fixes - In case of fire, break glass. External resources - Blog posts, slides and videos from users. Force output to a specific locale - Localisation issue Identify a Plugin or Theme Conflict - Debugging advice Installing WP-CLI Packages - Learn how to install packages and use version constraints Sharing WP-CLI Packages - Some words about your environment Troubleshooting Guide - Some help to troubleshoot Beginner's Guide So you've heard about WP-CLI and want to try it out, but you're not sure where to start? This guide is for you. No programming experience required! This guide is designed to be read from start to finish. Each section builds on the previous one, taking you from opening a terminal for the first time all the way to running your first WP-CLI commands and getting help when you need it. What Is the Command Line? When you use your computer, you normally interact with it by clicking on things — icons, buttons, menus. This is called a graphical user interface (GUI). The command line (also called the terminal, shell, or console) is a different way to interact with your computer. Instead of clicking, you type text commands and press Enter to run them. It might look intimidating at first, but it's really just another way to tell your computer what to do. Opening the Terminal On macOS: Press Command (⌘) + Space to open Spotlight Search. Type Terminal and press Enter. A window with a black or white background and a blinking cursor will appear. That's the terminal! On Windows: Press Windows key + R to open the Run dialog. Type cmd and press Enter. Alternatively, search for "Command Prompt" or "PowerShell" in the Start menu. Tip for Windows users: WP-CLI works best with Git Bash or Windows Subsystem for Linux (WSL). You can download Git Bash from git-scm.com. On Linux: Look for "Terminal" in your applications menu, or press Ctrl + Alt + T on many distributions. Basic Terminal Navigation Once the terminal is open, here are a few essential commands to know: Command What it does pwd Shows the folder you are currently in ls (macOS/Linux) or dir (Windows) Lists files and folders in the current folder cd folder-name Moves into a folder named folder-name cd .. Goes up one folder level clear Clears the terminal screen Don't worry — you don't need to memorize all of these right now. You can come back to this table whenever you need a reminder. What Is WP-CLI? WP-CLI is a command line interface for WordPress. It lets you manage your WordPress website by typing commands in the terminal, instead of clicking through the WordPress admin dashboard. Think of it this way: anything you can do in the WordPress admin (installing plugins, creating users, updating settings), you can also do with WP-CLI — and usually much faster. Why Should I Use WP-CLI? Here are a few reasons why even non-technical WordPress users find WP-CLI useful: Speed: Update all your plugins with a single command instead of clicking through each one. Automation: Repeat the same tasks easily without navigating the admin every time. Bulk actions: Do things to many posts, users, or options all at once. Remote management: Manage a website on a remote server without opening a browser. Reliability: Less clicking means fewer mistakes. Installing WP-CLI For step-by-step installation instructions on macOS, Linux, and Windows, see the Installing guide. It covers system requirements, the recommended Phar download method, and alternative installation options such as Composer and Homebrew. Running WP-CLI: Inside vs. Outside the WordPress Folder WP-CLI needs to know which WordPress installation to work with. By default, it looks for WordPress in your current directory (the folder your terminal is currently in). This means you generally need to cd into your WordPress site's folder before running WP-CLI commands. For example: cd /var/www/mysite wp plugin list If you run wp plugin list from a folder that doesn't contain a WordPress installation, you'll get an error like: Error: This does not seem to be a WordPress installation. Where is my WordPress folder? On a typical shared host, it's often something like /home/username/public_html or /var/www/html. If you use a local development tool like Local, MAMP, or XAMPP, each tool has its own folder structure — check its documentation. On managed WordPress hosts, WP-CLI may be pre-configured to work without needing to cd first. Running Your First WP-CLI Command Navigate in your terminal to your WordPress folder (see above). For example: cd /var/www/mysite Then try this command: wp --info This displays information about your WP-CLI installation. If it works, you're ready to go! To see the version of WordPress you have installed: wp core version To list the plugins on your site: wp plugin list Reading WP-CLI Output When you run a WP-CLI command, it prints a response in the terminal. Here's what the common messages mean: Success: — The command worked as expected. Warning: — The command ran, but there's something you should know about. Error: — Something went wrong. Read the message to find out what. Getting Help Built-in help WP-CLI has built-in documentation for every command. If you get stuck, run wp help to see a list of all available commands: wp help To get help on a specific command, add the command name after help. For example: wp help plugin wp help plugin install Community support If the built-in help isn't enough, these resources can help: WP-CLI Support — Search existing resources, ask a question, or report a reproducible bug. WP-CLI Handbook — The full reference documentation. GitHub Issues — Report bugs or search for known problems. WordPress.org Support Forums — General WordPress help from the community. Using WP-CLI on a Remote Server (SSH) If your WordPress site is hosted on a remote server (most live websites are), you can still use WP-CLI. There are two common approaches: SSH into the server and run WP-CLI there. Most hosts provide SSH access. Connect with a command like: ssh username@yoursite.com Then navigate to your WordPress folder and use WP-CLI normally. Use WP-CLI's built-in SSH support. WP-CLI can run commands on a remote server directly from your local machine. See the Running Commands Remotely guide for details. Learning More Now that you've got the basics, here are some great next steps: Common Tasks with WP-CLI — Practical examples for everyday WordPress management. Quick Start Guide — A short walkthrough of WP-CLI with more examples. Running Commands Remotely — Manage your live site's server over SSH. All WP-CLI Commands — The full list of commands available. External Resources — Blog posts, slides, and videos from WP-CLI users. You don't need to learn everything at once. Start with a task you want to speed up, find the right command, and go from there! Check status of a given plugin One of the check types included in wp doctor is Plugin_Status, or the ability to assert that a given plugin should be active, installed, or uninstalled. Although the check type isn't used by any of the default diagnostic checks, you can use the Plugin_Status check type in your custom doctor.yml configuration file. As an example, here are two checks using Plugin_Status, one which ensures Akismet is active on the system and another which ensures Hello Dolly is uninstalled: plugin-akismet-active: check: Plugin_Status options: name: akismet status: active plugin-hello-uninstalled: check: Plugin_Status options: name: hello status: uninstalled Run together, you might see: $ wp doctor check --config=plugin-status.yml --all +--------------------------+---------+----------------------------------------------------------------+ | name | status | message | +--------------------------+---------+----------------------------------------------------------------+ | plugin-akismet-active | success | Plugin 'akismet' is 'active' as expected. | | plugin-hello-uninstalled | error | Plugin 'hello' is 'inactive' but expected to be 'uninstalled'. | +--------------------------+---------+----------------------------------------------------------------+ The Plugin_Status check type accepts the following options: 'name': Name of the plugin as it appears in wp plugin list. 'status': Expected plugin status as one of 'active', 'installed', or 'uninstalled'. Commands Cookbook Creating your own custom WP-CLI command can be easier than it looks — and you can use wp scaffold package (repo) to dynamically generate everything but the command itself. Overview WP-CLI's goal is to offer a complete alternative to the WordPress admin; for any action you might want to perform in the WordPress admin, there should be an equivalent WP-CLI command. A command is an atomic unit of WP-CLI functionality. wp plugin install (doc) is one such command, as is wp plugin activate (doc). Commands are useful to WordPress users because they can offer simple, precise interfaces for performing complex tasks. But, the WordPress admin is a Swiss Army knife of infinite complexity. There's no way just this project could handle every use case. This is why WP-CLI includes a set of common internal commands, while also offering a rich internal API for third-parties to write and register their own commands. WP-CLI commands can be distributed as standalone packages, or bundled with WordPress plugins or themes. Standalone packages can be published as Composer packages and discovered through Packagist. You can use wp scaffold package (repo) to dynamically generate everything but the command itself. Packages are to WP-CLI as plugins are to WordPress. There are distinct differences in the approach you should take to creating a WP-CLI package. While WP-CLI is an ever-growing alternative to /wp-admin it is important to note that you must first write your package to work with the WP-CLI internal API before considering how you work with WordPress APIs. Command types Bundled commands: Usually cover functionality offered by a standard WordPress installation. There are exceptions to this rule though, notably wp search-replace (doc). Do not depend on other components such as plugins, themes etc. Are maintained by the WP-CLI team. Third-party commands: Can be defined in plugins or themes. Can be easily scaffolded as standalone projects with wp scaffold package (repo). Can be distributed independent of a plugin or theme as Composer packages, Git repositories, local paths, or ZIP files. All commands: Follow the documentation standards. Anatomy of a command WP-CLI supports registering any callable class, function, or closure as a command. WP_CLI::add_command() (doc) is used for both internal and third-party command registration. The synopsis of a command defines which positional and associative arguments a command accepts. Let's take a look at the synopsis for wp plugin install: $ wp plugin install usage: wp plugin install ... [--version=] [--force] [--activate] [--activate-network] In this example, ... is the accepted positional argument. In fact, wp plugin install accepts the same positional argument (the slug, ZIP, or URL of a plugin to install) multiple times. [--version=] is one of the accepted associative arguments. It's used to denote the version of the plugin to install. Notice, too, the square brackets around the argument definition; square brackets mean the argument is optional. WP-CLI also has a series of global arguments which work with all commands. For instance, including --debug means your command execution will display all PHP errors, and add extra verbosity to the WP-CLI bootstrap process. Required registration arguments When registering a command, WP_CLI::add_command() requires two arguments: $name is the command's name within WP-CLI's namespace (e.g. plugin install or post list). $callable is the implementation of the command, as a callable class, function, or closure. In the following example, each instance of wp foo is functionally equivalent: // 1. Command is a function function foo_command( $args ) { WP_CLI::success( $args[0] ); } WP_CLI::add_command( 'foo', 'foo_command' ); // 2. Command is a closure $foo_command = function( $args ) { WP_CLI::success( $args[0] ); }; WP_CLI::add_command( 'foo', $foo_command ); // 3. Command is a method on a class class Foo_Command { public function __invoke( $args ) { WP_CLI::success( $args[0] ); } } WP_CLI::add_command( 'foo', 'Foo_Command' ); // 4. Command is a method on a class with constructor arguments class Foo_Command { protected $bar; public function __construct( $bar ) { $this->bar = $bar; } public function __invoke( $args ) { WP_CLI::success( $this->bar . ':' . $args[0] ); } } $instance = new Foo_Command( 'Some text' ); WP_CLI::add_command( 'foo', $instance ); Importantly, classes behave a bit differently than functions and closures in that: Any public methods on a class are registered as subcommands of the command. For instance, given the examples above, a method bar() on the class Foo would be registered as wp foo bar. But... __invoke() is treated as a magic method. If a class implements __invoke(), the command name will be registered to that method and no other methods of that class will be registered as commands. Note: Historically, WP-CLI provided a base WP_CLI_Command class to extend, however extending this class is not required and will not change how your command behaves. All commands can be registered to their own top-level namespace (e.g. wp foo), or as subcommands to an existing namespace (e.g. wp core foo). For the latter, simply include the existing namespace as a part of the command definition. class Foo_Command { public function __invoke( $args ) { WP_CLI::success( $args[0] ); } } WP_CLI::add_command( 'core foo', 'Foo_Command' ); Quick and dirty execution Writing a short script for a one-off task, and don't need to register it formally with WP_CLI::add_command()? wp eval-file is your ticket (doc). Given a simple-command.php file: * : The name of the person to greet. * * [--type=] * : Whether or not to greet the person with success or error. * --- * default: success * options: * - success * - error * --- * * ## EXAMPLES * * wp example hello Newman * * @when after_wp_load */ function hello( $args, $assoc_args ) { list( $name ) = $args; // Print the message with type $type = $assoc_args['type']; WP_CLI::$type( "Hello, $name!" ); } } WP_CLI::add_command( 'example', 'Example_Command' ); This command's PHPDoc is interpreted in three ways: Shortdesc The shortdesc is the first line in the PHPDoc: /** * Prints a greeting. Longdesc The longdesc is middle part of the PHPDoc: * ## OPTIONS * * * : The name of the person to greet. * * [--type=] * : Whether or not to greet the person with success or error. * --- * default: success * options: * - success * - error * --- * * ## EXAMPLES * * wp example hello Newman Options defined in the longdesc are interpreted as the command's synopsis: is a required positional argument. Changing it to ... would mean the command can accept one or more positional arguments. Changing it to [] would mean that the positional argument is optional and finally, changing it to [...] would mean that the command can accept multiple optional positional arguments. [--type=] is an optional associative argument which defaults to 'success' and accepts either 'success' or 'error'. Changing it to [--error] would change the argument to behave as an optional boolean flag. [--field[=]] allows an optional argument to be used with or without a value. An example of this would be using a global parameter like --skip-plugins[=] which can either skip loading all plugins, or skip a comma-separated list of plugins. [--status=...] (note the trailing ...) declares a repeating associative argument. When the same flag is passed more than once (e.g. --status=active --status=inactive), WP-CLI collects the values into an array and $assoc_args['status'] will be [ 'active', 'inactive' ]. Without the ... ellipsis, passing the same flag multiple times uses last-wins behaviour—only the final value is kept. Boolean flags (e.g. [--verbose]) always use last-wins and are never collected into arrays, regardless of ellipsis. Note: To accept arbitrary/unlimited number of optional associative arguments you would use the annotation [--=]. So for example: * [--=] * : Allow unlimited number of associative parameters. A command's synopsis is used for validating the arguments, before passing them to the implementation. Argument aliases You can define one or more aliases for a parameter by appending them after a | separator inside the synopsis token. This lets users pass a short form (or any alternative name) and have WP-CLI automatically map it to the canonical argument name in $assoc_args. * [--with-dependencies|w] * : Include dependencies in the operation. With this definition, both --with-dependencies and -w are accepted, and $assoc_args['with-dependencies'] is populated in either case. Multiple aliases are separated by additional | characters: * [--verbose|v|wordy] * : Enable verbose output. Aliases work for value-carrying associative arguments too: * [--format=|f] * : Output format. Running wp example hello -f=json is then equivalent to wp example hello --format=json. Note that WP-CLI accepts both -=value (single-dash) and --=value (double-dash) forms for short-form aliases. YAML parameter options The --- block after a parameter description allows you to set extra metadata for that parameter using YAML. default sets the value used when the argument is omitted: * [--type=] * : Whether or not to greet the person with success or error. * --- * default: success * options: * - success * - error * --- options restricts the accepted values. WP-CLI validates user input against this list and returns an error for any value not in it. sensitive marks an argument as containing sensitive data (e.g. passwords, API keys): When the --prompt global flag is used, WP-CLI logs the full command it is about to run to STDOUT. Any argument flagged with sensitive: true will have its value replaced with [REDACTED] in that log line. This prevents secrets from leaking into logs or terminal history. * [--password=] * : Database password. * --- * sensitive: true * --- The longdesc is also displayed when calling the help command, for example, wp help example hello. Its syntax is Markdown Extra and here are a few more notes on how it's handled by WP-CLI: The longdesc is generally treated as a free-form text. The OPTIONS and EXAMPLES section names are not enforced, just common and recommended. Section names ( ## NAME) are colorized and printed with zero indentation. Everything else is indented by 2 characters, option descriptions are further indented by additional 2 characters. Word-wrapping is a bit tricky. If you want to utilize as much space on each line as possible and don't get word-wrapping artifacts like one or two words on the next line, follow these rules: Hard-wrap option descriptions at 75 chars after the colon and a space. Hard-wrap everything else at 90 chars. For more details on how you should format your command docs, please see WP-CLI's documentation standards. Docblock tags This is the last section and it starts immediately after the longdesc: * @when after_wp_load */ Here's the list of defined tags: @subcommand There are cases where you can't make the method name have the name of the subcommand. For example, you can't have a method named list, because list is a reserved keyword in PHP. That's when the @subcommand tag comes to the rescue: /** * @subcommand list */ function _list( $args, $assoc_args ) { ... } /** * @subcommand do-chores */ function do_chores( $args, $assoc_args ) { ... } @alias With the @alias tag, you can add another way of calling a subcommand. Example: /** * @alias hi */ function hello( $args, $assoc_args ) { ... } $ wp example hi Joe Success: Hello, Joe! @when This is a special tag that tells WP-CLI when to execute the command. It supports all registered WP-CLI hooks. Most WP-CLI commands execute after WordPress has loaded. The default behavior for a command is: @when after_wp_load To have a WP-CLI command run before WordPress loads, use: @when before_wp_load Do keep in mind most WP-CLI hooks fire before WordPress is loaded. If your command is loaded from a plugin or theme, @when will be essentially ignored. It has no effect if the command using it is loaded from a plugin or a theme, because by that time WordPress itself will have already been loaded. @skipglobalargcheck WP-CLI emits a warning at command-registration time if a command defines an argument whose name collides with an existing global argument (e.g. --debug, --user, --quiet). This helps command authors avoid confusing behaviour where the global argument takes precedence over the command-specific one. If you intentionally want to reuse a global argument name (for example, when wrapping another tool that uses the same flag), you can silence the warning with @skipglobalargcheck: /** * @skipglobalargcheck * @when before_wp_load */ function my_command( $args, $assoc_args ) { ... } WP_CLI::add_command()'s third $args parameter Each of the configuration options supported by PHPDoc can instead be passed as the third argument in command registration: $hello_command = function( $args, $assoc_args ) { list( $name ) = $args; $type = $assoc_args['type']; WP_CLI::$type( "Hello, $name!" ); if ( isset( $assoc_args['honk'] ) ) { WP_CLI::log( 'Honk!' ); } }; WP_CLI::add_command( 'example hello', $hello_command, array( 'shortdesc' => 'Prints a greeting.', 'synopsis' => array( array( 'type' => 'positional', 'name' => 'name', 'description' => 'The name of the person to greet.', 'optional' => false, 'repeating' => false, ), array( 'type' => 'assoc', 'name' => 'type', 'description' => 'Whether or not to greet the person with success or error.', 'optional' => true, 'default' => 'success', 'options' => array( 'success', 'error' ), ), array( 'type' => 'flag', 'name' => 'honk', 'optional' => true, ), ), 'when' => 'after_wp_load', 'longdesc' => '## EXAMPLES' . "\n\n" . 'wp example hello Newman', ) ); Note that the longdesc attribute will be appended to the description of the options generated from the synopsis, so this argument is great for adding examples of usage. If there is no synopsis, the longdesc attribute will be used as is to provide a description. Command internals Now that you know how to register a command, the world is your oyster. Inside your callback, your command can do whatever it wants. Accepting arguments In order to handle runtime arguments, you have to add two parameters to your callable: $args and $assoc_args. function hello( $args, $assoc_args ) { /* Code goes here*/ } The $args variable will store all the positional arguments: $ wp example hello Joe Doe WP_CLI::line( $args[0] ); // Joe WP_CLI::line( $args[1] ); // Doe The $assoc_args variable will store all the arguments defined like --key=value or --flag or --no-flag: $ wp example hello --name='Joe Doe' --verbose --no-option WP_CLI::line( $assoc_args['name'] ); // Joe Doe WP_CLI::line( $assoc_args['verbose'] ); // true WP_CLI::line( $assoc_args['option'] ); // false Also, you can combine argument types: $ wp example hello --name=Joe foo --verbose bar WP_CLI::line( $assoc_args['name'] ); // Joe WP_CLI::line( $assoc_args['verbose'] ); // true WP_CLI::line( $args[0] ); // foo WP_CLI::line( $args[1] ); // bar Effectively reusing WP-CLI internal APIs As an example, say you were tasked with finding all unused themes on a multisite network (#2523). If you had to perform this task manually through the WordPress admin, it would probably take hours, if not days, of effort. However, if you're familiar with writing WP-CLI commands, you could complete the task in 15 minutes or less. Here's what such a command looks like: /** * Find unused themes on a multisite network. * * Iterates through all sites on a network to find themes which aren't enabled * on any site. */ $find_unused_themes_command = function() { $response = WP_CLI::launch_self( 'site list', array(), array( 'format' => 'json' ), false, true ); $sites = json_decode( $response->stdout ); $unused = array(); $used = array(); foreach( $sites as $site ) { WP_CLI::log( "Checking {$site->url} for unused themes..." ); $response = WP_CLI::launch_self( 'theme list', array(), array( 'url' => $site->url, 'format' => 'json' ), false, true ); $themes = json_decode( $response->stdout ); foreach( $themes as $theme ) { if ( 'no' == $theme->enabled && 'inactive' == $theme->status && ! in_array( $theme->name, $used ) ) { $unused[ $theme->name ] = $theme; } else { if ( isset( $unused[ $theme->name ] ) ) { unset( $unused[ $theme->name ] ); } $used[] = $theme->name; } } } WP_CLI\Utils\format_items( 'table', $unused, array( 'name', 'version' ) ); }; WP_CLI::add_command( 'find-unused-themes', $find_unused_themes_command, array( 'before_invoke' => function(){ if ( ! is_multisite() ) { WP_CLI::error( 'This is not a multisite installation.' ); } }, ) ); Let's run through the internal APIs this command uses to achieve its goal: WP_CLI::add_command() (doc) is used to register a find-unused-themes command to the $find_unused_themes_command closure. The before_invoke argument makes it possible to verify the command is running on a multisite install, and error if not. WP_CLI::error() (doc) renders a nicely formatted error message and exits. WP_CLI::launch_self() (doc) initially spawns a process to get a list of all sites, then is later used to get the list of themes for a given site. WP_CLI::log() (doc) renders informational output to the end user. WP_CLI\Utils\format_items() (doc) renders the list of unused themes after the command has completed its discovery. Help rendering Your command's PHPDoc (or registered definition) is rendered using the help command. The output is ordered like this: Short description Synopsis Long description (OPTIONS, EXAMPLES etc.) Global parameters Writing tests WP-CLI makes use of a Behat-based testing framework, which you should use too. Behat is a great choice for your WP-CLI commands because: It's easy to write new tests, which means they'll actually get written. The tests interface with your command in the same manner as your users interface with your command. Behat tests live in the features/ directory of your project. Here's an example from features/cli-info.feature: Feature: Review CLI information Scenario: Get the path to the packages directory Given an empty directory When I run `wp cli info --format=json` Then STDOUT should be JSON containing: """ {"wp_cli_packages_dir_path":"/tmp/wp-cli-home/.wp-cli/packages/"} """ When I run `WP_CLI_PACKAGES_DIR=/tmp/packages wp cli info --format=json` Then STDOUT should be JSON containing: """ {"wp_cli_packages_dir_path":"/tmp/packages/"} """ Functional tests typically follow this pattern: Given some background, When a user performs a specific action, Then the end result should be X (and Y and Z). Convinced? Head on over to wp-cli/scaffold-package-command to get started. Adding commands globally If you want to have custom commands available globally, without needing to create a full plugin or package, you can use WP-CLI's require configuration option. Edit your global config file (usually found at ~/.wp-cli/config.yml) to require a PHP file: require: - ~/.wp-cli/commands.php Then create the file and add your custom commands: flag on the command line, or set the WP_CLI_REQUIRE environment variable, to load a custom PHP file for a single invocation. Distribution Now that you've produced a command you're proud of, it's time to share it with the world. There are two common ways of doing so. Include in a plugin or theme One way to share WP-CLI commands is by packaging them in your plugin or theme. Many people do so by conditionally loading (and registering) the command based on the presence of the WP_CLI constant. if ( defined( 'WP_CLI' ) && WP_CLI ) { require_once dirname( __FILE__ ) . '/inc/class-plugin-cli-command.php'; } Alternatively, you can use the cli_init hook to register your commands. This hook is fired during the WP-CLI Runner startup process, providing a dedicated event for command registration without needing to check for the WP_CLI constant. /** * Register custom WP-CLI commands using the cli_init hook. */ function myplugin_register_cli_commands() { require_once dirname( __FILE__ ) . '/inc/class-plugin-cli-command.php'; WP_CLI::add_command( 'myplugin', 'MyPlugin_CLI_Command' ); } add_action( 'cli_init', 'myplugin_register_cli_commands' ); Both approaches are valid. Use the WP_CLI constant check when you need to conditionally load code based on whether WP-CLI is present. Use the cli_init hook when you want to hook into a specific point in the WP-CLI initialization process. Distribute as a stand-alone command Standalone WP-CLI commands can be installed from any git repository, ZIP file or folder. The only technical requirement is to include a valid composer.json file with an autoload declaration. We recommend including "type": "wp-cli-package" to distinguish your project explicitly as a WP-CLI package. Here's a full composer.json example from the server command: { "name": "wp-cli/server-command", "description": "Start a development server for WordPress", "type": "wp-cli-package", "homepage": "https://github.com/wp-cli/server-command", "license": "MIT", "authors": [ { "name": "Package Maintainer", "email": "packagemaintainer@homepage.com", "homepage": "https://www.homepage.com" } ], "require": { "php": ">=5.3.29" }, "autoload": { "files": [ "command.php" ] } } Note the autoload declaration, which loads command.php. Once you've added a valid composer.json file to your project repository, WP-CLI users can pull it in via the package manager from the location you opted to store it in. Here are a few examples of storage locations and the corresponding syntax of installing it via the package manager: Git repository To install a package that is found in a git repository, you can provide either the HTTPS or the SSH link to the git repository to the package install command. # Installing the package using an HTTPS link $ wp package install https://github.com/wp-cli/server-command.git # Installing the package using an SSH link $ wp package install git@github.com:wp-cli/server-command.git ZIP file You can install a package from a ZIP file by providing the path to that file to the wp package install command. # Installing the package using a ZIP file $ wp package install ~/Downloads/server-command-main.zip Common Issues Error: Can't connect to the database A few possibilities: a) you're using MAMP, but WP-CLI is not using the MAMP PHP binary. You can check which PHP WP-CLI is using by running wp --info. If you need to specify an alternate PHP binary, see using a custom PHP binary. b) it's a WordPress multisite install. c) the database credentials in wp-config.php are actually incorrect. Running wp --info produces HTML output If you run wp --info on a server with Phar support disabled, you may see: $ wp --info Content-type: text/html; charset=UTF-8 When using the WP-CLI Phar, you'll need to whitelist Phar support in your php.ini: suhosin.executor.include.whitelist = phar PHP Fatal error: Cannot redeclare wp_unregister_GLOBALS() If you get this fatal error running the wp command, you may have moved or edited wp-config.php beyond what wp-cli supports: PHP Fatal error: Cannot redeclare wp_unregister_GLOBALS() (previously declared in /var/www/foo.com/wp-includes/load.php:18) in /var/www/foo.com/wp-includes/load.php on line 33 One of WP-CLI's requirements is that the line: require_once(ABSPATH . 'wp-settings.php'); remains in the wp-config.php file, so if you've modified or moved it, put it back there. It gets matched by a regex when WP-CLI runs. PHP Fatal error: Call to undefined function Before WP-CLI can load wp-settings.php, it needs to know all of the constants defined in wp-config.php (database connection details and so on). Because WP-CLI doesn't want WordPress to load yet when it's pulling the constants out of wp-config.php, it uses regex to strip the require_once(ABSPATH . 'wp-settings.php'); statement. If you've modified your wp-config.php in a way that calls WordPress functions, PHP will fail out with a fatal error, as your wp-config.php is calling a WordPress function before WordPress has been loaded to define it. Example: $ wp core check-update PHP Fatal error: Call to undefined function add_filter() in phar:///usr/local/bin/wp/php/WP_CLI/Runner.php(952) : eval()'d code on line N Modifying wp-config.php beyond constant definitions is not best practice. You should move any modifications to a WordPress mu-plugin, which will retain the functionality of your modifications while allowing wp-cli to parse your wp-config.php without throwing a PHP error, as well as preventing other errors. See: #1631 PHP Fatal error: Call to undefined function cli\posix_isatty() Please ensure you have the php-process extension installed. For example for Centos 6: yum install php-process PHP Fatal error: Allowed memory size of 999999 bytes exhausted (tried to allocate 99 bytes) If you run into a PHP fatal error relating to memory when running wp package install, you're likely running out of memory. WP-CLI uses Composer under the hood to manage WP-CLI packages. However, Composer is a bit of a memory hog, so you'll need to increase your memory limit to accommodate it. Edit your php.ini as a permanent fix: # Find your php.ini for PHP-CLI $ php -i | grep php.ini Configuration File (php.ini) Path => /usr/local/etc/php/7.0 Loaded Configuration File => /usr/local/etc/php/7.0/php.ini # Increase memory_limit to 512M or greater $ vim /usr/local/etc/php/7.0/php.ini memory_limit = 512M Set memory_limit on the fly as a temporary fix: $ php -d memory_limit=512M "$(which wp)" package install If your PHP process is still running out of memory, try these steps: Restart PHP. Check for additional php.ini files: $ php -i | grep additional Scan this dir for additional .ini files => /usr/local/etc/php/7.1/conf.d # Edit the additional file(s) and increase the memory_limit to 512M or greater $ vim /usr/local/etc/php/7.1/conf.d memory_limit = 512M Error: YIKES! It looks like you're running this as root. Running WP-CLI as root is extremely dangerous. When you execute WP-CLI as root, any code within your WordPress instance (including third-party plugins and themes you've installed) will have full privileges to the entire server. This can enable malicious code within the WordPress instance to compromise the entire server. The WP-CLI project strongly discourages running WP-CLI as root. See also: #973 PHP notice: Undefined index on $_SERVER superglobal The $_SERVER superglobal is an array typically populated by a web server with information such as headers, paths, and script locations. PHP CLI doesn't populate this variable, nor does WP-CLI, because many of the variable details are meaningless at the command line. Before accessing a value on the $_SERVER superglobal, you should check if the key is set: if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) { $_SERVER['HTTPS']='on'; } When using $_SERVER['HTTP_HOST'] in your wp-config.php, you'll need to set a default value in WP-CLI context: if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( $_SERVER['HTTP_HOST'] ) ) { $_SERVER['HTTP_HOST'] = 'example.com'; } See also: #730 PHP notice: Use of undefined constant STDOUT The STDOUT constant is defined by the PHP CLI. If you receive an error notice that STDOUT is missing, it's likely because you're not running WP-CLI by PHP CLI. Please review your server configuration accordingly. PHP Parse error: syntax error, unexpected '?' in ... /php/WP_CLI/Runner.php ... eval()'d code on line 1 If you get this error running the wp command, the most likely cause is a Unicode BOM at the start of your wp-config.php. This issue will be addressed in a future release of WP-CLI, but in the meantime you can solve the issue by running: $ sed -i '1s/^\xEF\xBB\xBF//' $(wp config path) or by manually removing the BOM using your favorite editor. See also: wp-cli/search-replace-command#71 Can't find wp-content directory / use of $_SERVER['document_root'] $_SERVER['document_root'] is defined by the webserver based on the incoming web request. Because this type of context is unavailable to PHP CLI, $_SERVER['document_root'] is unavailable to WP-CLI. Furthermore, WP-CLI can't safely mock $_SERVER['document_root'] as it does with $_SERVER['http_host'] and a few other $_SERVER values. If you're using $_SERVER['document_root'] in your wp-config.php file, you should instead use dirname( __FILE__ ) or similar. See also: #785 Conflict between global parameters and command arguments All of the global parameters (e.g. --url=) may conflict with the arguments you'd like to accept for your command. For instance, adding a RSS widget to a sidebar will not populate the feed URL for that widget: $ wp widget add rss sidebar-1 1 --url="http://www.smashingmagazine.com/feed/" --items=3 Success: Added widget to sidebar. Expected result: widget has the feed URL set. Actual result: widget is added with the number of items set to 3, but with empty feed URL. Use the WP_CLI_STRICT_ARGS_MODE environment variable to tell WP-CLI to treat any arguments before the command as global, and after the command as local: WP_CLI_STRICT_ARGS_MODE=1 wp --url=wp.dev/site2 widget add rss sidebar-1 1 --url="http://wp-cli.org/feed/" In this example, --url=wp.dev/site2 is the global argument, setting WP-CLI to run against 'site2' on a WP multisite install. --url="http://wp-cli.org/feed/" is the local argument, setting the RSS feed widget with the proper URL. See also: #3128 Warning: Some code is trying to do a URL redirect Most of the time, it's some plugin or theme code that disables wp-admin access to non-admins. Quick fix, other than disabling the protection, is to pass the user parameter: --user=some_admin See also: #477 Cannot create a post with Latin characters in the title on Windows Considering the following example: wp post create --post_title="Perícias Contábeis" Using UTF-8 in PHP arguments doesn't work on Windows for PHP <= 7.0, however it will work for PHP >= 7.1, as it was fixed as part of Support for long and UTF-8 path. A workaround for PHP <= 7.0 is to use the --prompt option: echo "Perícias Contábeis" | wp post create --post_type=page --post_status=publish --prompt=post_title See also: #4714 Arguments containing newlines are truncated on Windows On Windows, wp is a .bat wrapper around php wp-cli.phar. Batch files terminate a command at a newline, including a newline inside a quoted argument, because cmd.exe splits the line before quoting is evaluated. Any flags positioned after the multi-line value never reach WP-CLI. The command still succeeds with the arguments it did receive, so there is no error — WordPress silently applies its defaults for the missing values. In the example below the post is created, but as a draft under the site's default category and attributed to the current user, because everything after --post_content ( --post_category, --post_author and --post_status) was dropped: wp post create --post_content="" --post_category=3 --post_author=2 --post_status=publish Pass the content over STDIN instead, using - as the positional argument. The newline then lives in the piped stream rather than in the command line, so nothing is truncated: type content.html | wp post create - --post_status=publish --post_category=3 --post_author=2 The same applies in PowerShell: $content | wp post create - --post_status=publish --post_category=3 --post_author=2 wp post create and wp post update also accept a file path in place of -. See also: #2712 The installation hangs If the installation seems to hang forever while trying to clone the resources from GitHub, please ensure that you are allowed to connect to Github using SSL (port 443) and Git (port 9418) for outbound connections. W3 Total Cache Error: some files appear to be missing or out of place. W3 Total Cache object caching can cause this problem. Disabling object caching and optionally removing wp-content/object-cache.php will allow WP-CLI to work again. See also: #587 The automated updater doesn't work for versions before 3.4 The wp core update command is designed to work for WordPress 3.4 and above. To be able to update an older website to latest WordPress, you could try one of the following alternatives: Fully-automated: Run wp core download --force to download latest WordPress and replace it with your files (don't worry, wp-config.php will remain intact). Then, run wp core update-db to update the database. Since the procedure isn't ideal, run once again wp core download --force and the new version should be available. Semi-automated: Run wp core download --force to download all files and replace them in your current installation, then navigate to /wp-admin/ and run the database upgrade when prompted. PHP Fatal error: Maximum function nesting level of '#' reached, aborting! You're encountering a limitation associated with PHP Xdebug. Here are a few solutions to the problem: Temporarily turn off Xdebug while executing the CLI command: XDEBUG_MODE=off wp ... Increase the value of xdebug.max_nesting_level in your php.ini file. You can learn more about it here - xdebug.max-nesting-level If you're looking for a quick fix while still retaining debugging tools, you can change the max_nesting_level value to execute the command just once: php -d xdebug.max_nesting_level=512 wp ... If you're not in the process of debugging code, it's advisable to completely disable Xdebug. For instructions on how to permanently switch off Xdebug, please refer to the documentation of your local environment or your hosting provider. You might experience similar issues with the alternate error message: Error: Xdebug has detected a possible infinite loop, and aborted your script with a stack depth of '#' frames Common Tasks with WP-CLI Not sure what you can do with WP-CLI? This page showcases practical, everyday WordPress management tasks you can accomplish from the command line — with examples you can use right away. New to the command line? Start with the Beginner's Guide first. Plugins Install a plugin wp plugin install contact-form-7 Install and activate a plugin in one step wp plugin install contact-form-7 --activate Activate an already-installed plugin wp plugin activate contact-form-7 Deactivate a plugin wp plugin deactivate contact-form-7 Update a specific plugin wp plugin update akismet Update all plugins at once wp plugin update --all List all installed plugins and their status wp plugin list Delete a plugin wp plugin delete hello Themes Install a theme wp theme install twentytwentyfour Activate a theme wp theme activate twentytwentyfour Update all themes wp theme update --all List all installed themes wp theme list WordPress Core Check which version of WordPress is installed wp core version Check if a WordPress update is available wp core check-update Update WordPress to the latest version wp core update Update the WordPress database after an update wp core update-db Users List all users wp user list Create a new user wp user create jane jane@example.com --role=editor Change a user's password wp user update jane --user_pass=newpassword Delete a user wp user delete jane --reassign=1 Tip: The --reassign=1 option moves the deleted user's posts to the user with ID 1 (usually the site admin). Replace 1 with the ID of the user you want to reassign posts to. Add an administrator role to an existing user wp user set-role jane administrator Posts and Pages Create a new post wp post create --post_title="My First Post" --post_status=publish --post_type=post Create a new page wp post create --post_title="About Us" --post_status=publish --post_type=page List all published posts wp post list --post_status=publish Delete a post (sends it to trash) wp post delete 42 Permanently delete a post wp post delete 42 --force Comments List all comments awaiting moderation wp comment list --status=hold Approve a comment wp comment approve 5 Delete spam comments wp comment delete $(wp comment list --status=spam --format=ids) Note: The $(...) syntax works in bash (macOS, Linux, Git Bash, or WSL on Windows). It does not work in Windows Command Prompt. Windows users should use Git Bash, WSL, or PowerShell to run this command. Options and Settings Get the site URL wp option get siteurl Get the site title wp option get blogname Change the site title wp option update blogname "My New Site Title" Discourage search engines from indexing the site wp option update blog_public 0 Re-enable search engine indexing wp option update blog_public 1 Search and Replace Preview what would change when moving a site to a new domain wp search-replace 'http://oldsite.com' 'http://newsite.com' --dry-run Replace URLs when moving to a new domain wp search-replace 'http://oldsite.com' 'http://newsite.com' Media Regenerate all thumbnail sizes Useful after changing your theme or adding a new image size: wp media regenerate --yes Caching Flush the object cache wp cache flush Flush rewrite rules (useful after adding a new plugin or changing permalink settings) wp rewrite flush Database Export the database to a file wp db export backup.sql Import a database from a file wp db import backup.sql Open an interactive database console wp db cli WP-CLI Itself Check the installed version of WP-CLI wp cli version Update WP-CLI to the latest version wp cli update Get system information useful for troubleshooting wp --info Going Further These examples only scratch the surface. WP-CLI has commands for nearly every part of WordPress. To explore more: Browse the full list of built-in commands. Read the Quick Start Guide for a more detailed walkthrough. Check out External Resources for tutorials, blog posts, and videos. Customize doctor diagnostic checks Even though wp doctor comes with a number of default diagnostic checks, it's designed with extensibility at its core. Checks are defined at runtime, read from a doctor.yml configuration file naming each check with its options. doctor.yml format Let's take a look at the first two checks in the included doctor.yml: autoload-options-size: check: Autoload_Options_Size constant-savequeries-falsy: check: Constant_Definition options: constant: SAVEQUERIES falsy: true In this example: 'autoload-options-size' and 'constant-savequeries-falsy' are the names for the checks. Names must be unique amongst all registered checks. Autoload_Options_Size and Constant_Definition are reusable check classes in the runcommand\Doctor\Checks namespace. You can use them too, or you can write your own class extending runcommand\Doctor\Checks\Check and supply it as 'class: yourNamespace\yourClassName'. 'constant' and 'falsy' are configuration options accepted by the Constant_Definition class. In this case, we're telling doctor to ensure SAVEQUERIES is either false or undefined. For the sake of completeness, it's also worth noting Autoload_Options_Size accepts 'threshold_kb' as an optional configuration option. The default value for 'threshold_kb' is 900, so it doesn't need to be included in the doctor.yml. Custom doctor.yml configuration files Run your own doctor checks by creating a doctor.yml and supplying it with wp doctor check --config=doctor.yml. Use different configurations for different environments by creating separate prod.yml and dev.yml files. If you want your custom file to extend an existing doctor config, you can use the magical _ config file option to define which config file to inherit. 'default' is a magic reference to the bundled doctor.yml; you also specify an entire file path. Take a look at this example: _: inherit: default skipped_checks: - autoload-options-size constant-disallow-file-mods-falsy: check: Constant_Definition options: constant: DISALLOW_FILE_MODS falsy: true plugin-akismet-active: check: Plugin_Status options: plugin: akismet status: active plugin-akismet-valid-api-key: class: Akismet_Valid_API_Key require: akismet-valid-api-key.php This custom doctor.yml file: Inherits all default diagnostic checks except for 'autoload-options-size'. Defines a 'constant-disallow-file-mods-falsy' check to ensure the DISALLOW_FILE_MODS constant is falsy. Defines a 'plugin-akismet-active' check to ensure Akismet is active. Defines a 'plugin-akismet-valid-api-key' custom check in a akismet-valid-api-key.php file to ensure Akismet has a valid API key. Available check types Some wp doctor check types are configurable, meaning the default setting can be changed, while other check types are abstracted in such a way that they can be reusable. For instance, the Autoload_Options_Size check accepts an option 'threshold_kb' while Plugin_Status accepts 'plugin' and 'status' as options. The configurable check types include: check: Autoload_Options_Size: Accepts 'threshold_kb' as an option to set the threshold in kilobytes. Default value is 900. check: Cron_Count: Accepts 'threshold_count' as an option to set the threshold of total cron jobs. Default value is 50. check: Cron_Duplicates: Accepts 'threshold_count' as an option to set the threshold of duplicate cron jobs. Default value is 10. check: Plugin_Active_Count: Accepts 'threshold_count' as an option to set the threshold of total active plugins. Default is 80. check: Plugin_Deactivated: Accepts 'threshold_percentage' as an option to set the threshold of percentage deactivated plugins. Default is 40. The abstracted check types include: check: Constant_Definition: Assert a given constant as defined, a specific value, or falsy. Learn more. check: File_Contents: Check all or a selection of WordPress files for a given regex pattern. Learn more. check: Option_Value: Assert a given option as a specific value. Learn more. check: Plugin_Status: Assert a given plugin as active, installed, or uninstalled. Learn more. Of course, some check types don't need configuration options: check: Core_Update: Errors when new WordPress minor release is available; warns for major release. check: Core_Verify_Checksums: Verifies WordPress files against published checksums; errors on failure. check: Plugin_Update: Warns when there are plugin updates available. check: Theme_Update: Warns when there are theme updates available. You can write your own custom check type by extending the runcommand\Doctor\Checks\Check class. Default doctor diagnostic checks Although its power comes from its ability to be customized, wp doctor includes a number of default diagnostic checks considered to be recommendations for production websites. Use wp doctor list to view these default checks: name description autoload-options-size Warns when autoloaded options size exceeds threshold of 900 kb. constant-savequeries-falsy Confirms expected state of the SAVEQUERIES constant. constant-wp-debug-falsy Confirms expected state of the WP_DEBUG constant. core-update Errors when new WordPress minor release is available; warns for major release. core-verify-checksums Verifies WordPress files against published checksums; errors on failure. cron-count Errors when there's an excess of 50 total cron jobs registered. cron-duplicates Errors when there's an excess of 10 duplicate cron jobs registered. file-eval Checks files on the filesystem for regex pattern `eval\(.*base64_decode\(.*`. option-blog-public Confirms the expected value of the 'blog_public' option. plugin-active-count Warns when there are greater than 80 plugins activated. plugin-deactivated Warns when greater than 40% of plugins are deactivated. plugin-update Warns when there are plugin updates available. theme-update Warns when there are theme updates available. To explain these further: Autoloaded options are options that are automatically loaded in every request to WordPress. A size exceeding the recommended threshold could be a symptom of a larger problem. Because SAVEQUERIES causes WordPress to save a backtrace for every SQL query, which is an expensive operation, using SAVEQUERIES in production is discouraged. WordPress minor versions are typically security releases that should be applied immediately. If you create a custom doctor.yml config file, you can use wp doctor list --config= to view the diagnostic checks listed in the file. Doctor Guides Here are some helpful guides for using wp doctor: Default doctor diagnostic checks Customize doctor diagnostic checks Write a custom check to perform an arbitrary assertion Write a check for verifying contents of WordPress files Write a check for asserting the value of a given option Write a check for asserting the value of a given constant Check status of a given plugin External Resources Blog posts & tutorials scribu: A Command-Line Interface for WordPress madewithlove: WP-CLI: WordPress Command Line Tools Mika Epstein: Command Line WP Treehouse Blog: Tame WordPress from the Command Line with wp-cli Matt Wiebe: Why You Should Develop Plugins With wp-cli Torque: Using WP CLI to Set Up a Test Version of Your Site WP Bullet Guides: Useful Bash Scripts Utilizing WP-CLI Delicious Brains: Writing Functional Tests for WP-CLI Packages Slides Francesco Laffi: WPday Bologna 2013: wp-cli Michael Bastos: Command Line WordPress Step by Step Kelly Dwan: Importing (any!) Content with WP-CLI Jaime Martinez: Simplify your day to day tasks with WP-CLI Videos scribu: Command Line Learnings For Make Benefit Glorious Nation Of WordPress Mike Schroder: Magical WordPress Management using WP-CLI Daniel Bachhuber: WordPress at the Command Line AJ Morris: Managing WordPress from the Command Line Shawn Hooper: WP-CLI – Save Time by Managing WordPress from the Command Line Force output to a specific locale WP-CLI always outputs English because it doesn't support localization. But, because WordPress supports localization, you may see non-English output when performing specific commands. For instance: $ wp theme update --all מעבר למצב תחזוקה... מוריד עדכונים מ-https://downloads.wordpress.org/theme/hueman.3.3.25.zip... Using cached file '/home/xxx/.wp-cli/cache/theme/hueman-3.3.25.zip'... פתיחת עדכון... התקנת גרסה חדשה... הסרת הגרסה הקודמת של התבנית... התבנית עודכנה בהצלחה. ביטול מצב תחזוקה... +--------+-------------+-------------+---------+ | name | old_version | new_version | status | +--------+-------------+-------------+---------+ | hueman | 3.3.24 | 3.3.25 | Updated | +--------+-------------+-------------+---------+ Success: Updated 1 of 1 themes. To force WordPress to always output English at the command line, you need to filter the active locale. Given a force-locale.php file: /dev/null || printf '%s\n' "$0") d=${script_path%[/\\]*} [ "$d" = "$script_path" ] && d=. dir=$(cd "$d" && pwd) # See if we are running in Cygwin by checking for cygpath program if command -v 'cygpath' >/dev/null 2>&1; then # Cygwin paths start with /cygdrive/ which will break windows PHP, # so we need to translate the dir path to windows format. However # we could be using cygwin PHP which does not require this, so we # test if the path to PHP starts with /cygdrive/ rather than /usr/bin if [[ $(which php) == /cygdrive/* ]]; then dir=$(cygpath -m "$dir") fi fi php "${dir}/wp-cli.phar" "$@" Then make the file executable: chmod +x /c/wp-cli/wp You can now use wp from Git Bash. Installing via .deb package On Debian or Ubuntu, just download and open one of the .deb packages: https://github.com/wp-cli/builds/tree/gh-pages/deb Installing on Fedora 30+ su -c 'dnf install wp-cli' Installing on CentOS su -c 'yum install wp-cli' Using a custom PHP binary In some cases, like for MAMP installs, you might not want to use the default PHP binary. To use the latest PHP version provided by MAMP, you'll need to modify your PATH environment variable with the following added to your ~/.bash_profile or ~/.zsh_profile: PHP_VERSION=$(ls /Applications/MAMP/bin/php/ | sort -n | tail -1) export PATH=/Applications/MAMP/bin/php/${PHP_VERSION}/bin:$PATH To use a specific PHP version provided by MAMP, you'll need to determine the path to the PHP version's executable, and modify your PATH environment variable with the following added to your ~/.bash_profile or ~/.zsh_profile: export PATH=/Applications/MAMP/bin/php/php5.5.26/bin:$PATH Note there's no assignment of the PHP_VERSION variable in this case, because we aren't dynamically looking up the latest PHP version. Once you have added that and saved the file, reload the file with: source ~/.bash_profile After you've done that, run wp --info to make sure the change has been applied correctly. For Composer and Git-based WP-CLI installation, you can alternatively set the WP_CLI_PHP environment variable, if you don't want to modify PATH for some reason. Installing WP-CLI Packages WP-CLI packages are community-maintained projects built on WP-CLI. They can contain WP-CLI commands, but they can also just extend WP-CLI in some way. To find existing packages to install, take a look at Packagist. Installing a Package Use the wp package install command to install a package. Packages can be specified as: Composer package name Git URL accessible by the current shell user Path to a directory on the local machine Local or remote .zip file # Install a package by name wp package install wp-cli/server-command # Install from a Git URL wp package install https://github.com/wp-cli/server-command.git # Install from a local directory wp package install /path/to/package # Install from a .zip file wp package install package.zip Packages are installed to ~/.wp-cli/packages/ by default. Use the WP_CLI_PACKAGES_DIR environment variable to provide a custom path. See also: Sharing WP-CLI Packages. Version Constraints When installing WP-CLI packages, you can specify version constraints to control which version of a package is installed. Version constraints are specified by appending a colon ( :) followed by the constraint to the package name: wp package install : WP-CLI uses Composer's version constraint syntax. Here are some commonly used examples: # Install a specific version wp package install wp-cli/server-command:2.0.0 # Install with caret operator (allows non-breaking updates) wp package install wp-cli/server-command:^1.0 # Install with tilde operator (allows patch-level updates) wp package install wp-cli/server-command:~1.2 # Install the latest stable release wp package install wp-cli/server-command:@stable # Install from a development branch wp package install wp-cli/server-command:dev-main For complete documentation on version constraint syntax and operators, see the Composer documentation on versions and constraints. Quick Start Congratulations! You've installed WP-CLI for the first time, and are ready to level-up your use of WordPress. This page contains a brief introduction to WP-CLI with some example usage. Introduction WP-CLI is a command line interface for WordPress. The project's goal is to offer a complete alternative to the WordPress admin; for any action you might want to perform in the WordPress admin, there should be an equivalent WP-CLI command. For instance, because you can install a plugin from the WordPress admin, you can also install a plugin with WP-CLI: $ wp plugin install akismet Installing Akismet (3.1.8) Downloading install package from https://downloads.wordpress.org/plugin/akismet.3.1.8.zip... Unpacking the package... Installing the plugin... Plugin installed successfully. And, because you can also activate plugins from the WordPress admin, you can activate a plugin with WP-CLI: $ wp plugin activate akismet Success: Plugin 'akismet' activated. One key difference between using the WordPress admin and WP-CLI: performing any action takes many fewer clicks. As you become more familiar with the command line, you'll notice performing a given task with WP-CLI is generally much faster than performing the same task through the WordPress admin. Investing time upfront into learning how to better use WP-CLI pays dividends in the long term. Common Terms Throughout your usage of WP-CLI, you'll hear certain terms used over and over again. For instance, a command is an atomic unit of WP-CLI functionality. wp plugin install is one such command, as is wp plugin activate. Commands represent a name (e.g. 'plugin install') and a callback, and are registered with WP_CLI::add_command() (doc). The synopsis defines which positional and associative arguments a command accepts. Let's take a look at the synopsis for wp plugin install: $ wp plugin install usage: wp plugin install ... [--version=] [--force] [--activate] [--activate-network] In this example, ... is the accepted positional argument. In fact, wp plugin install accepts the same positional argument (the slug, ZIP, or URL of a plugin to install) multiple times. [--version=] is one of the accepted associative arguments. It's used to denote the version of the plugin to install. Notice, too, the square brackets around the argument definition; square brackets mean the argument is optional. WP-CLI also has a series of global arguments which work with all commands. For instance, including --debug means your command execution will display all PHP errors, and add extra verbosity to the WP-CLI bootstrap process. Practical Examples Ready to dive in? Here are some common examples of how WP-CLI is used: Download and install WordPress in seconds Download the latest version of WordPress with wp core download (doc). $ wp core download --path=wpclidemo.dev Creating directory '/srv/www/wpclidemo.dev/'. Downloading WordPress 4.6.1 (en_US)... Using cached file '/home/vagrant/.wp-cli/cache/core/wordpress-4.6.1-en_US.tar.gz'... Success: WordPress downloaded. Create a new wp-config.php file with wp config create (doc). $ cd wpclidemo.dev $ wp config create --dbname=wpclidemo --dbuser=root --prompt=dbpass 1/10 [--dbpass=]: Success: Generated 'wp-config.php' file. Create the database based on wp-config.php with wp db create (doc). $ wp db create Success: Database created. Install WordPress with wp core install (doc). $ wp core install --url=wpclidemo.dev --title="WP-CLI" --admin_user=wpcli --admin_password=wpcli --admin_email=info@wp-cli.org Success: WordPress installed successfully. That's it! Update plugins to their latest version Use wp plugin update --all (doc) to update all plugins to their latest version. $ wp plugin update --all Enabling Maintenance mode... Downloading update from https://downloads.wordpress.org/plugin/akismet.3.1.11.zip... Unpacking the update... Installing the latest version... Removing the old version of the plugin... Plugin updated successfully. Downloading update from https://downloads.wordpress.org/plugin/nginx-champuru.3.2.0.zip... Unpacking the update... Installing the latest version... Removing the old version of the plugin... Plugin updated successfully. Disabling Maintenance mode... Success: Updated 2/2 plugins. +------------------------+-------------+-------------+---------+ | name | old_version | new_version | status | +------------------------+-------------+-------------+---------+ | akismet | 3.1.3 | 3.1.11 | Updated | | nginx-cache-controller | 3.1.1 | 3.2.0 | Updated | +------------------------+-------------+-------------+---------+ Add a user as a super-admin On multisite, use wp super-admin add (doc) to grant super admin capabilities to an existing user. $ wp super-admin add wpcli Success: Granted super-admin capabilities. Regenerate thumbnails If you've added or changed an image size registered with add_image_size(), you may want to use wp media regenerate (doc) so your theme displays the correct image size. wp media regenerate --yes Found 1 image to regenerate. 1/1 Regenerated thumbnails for "charlie-gpa" (ID 4). Success: Finished regenerating the image. Search and replace URLs If you're moving a database from one domain to another, wp search-replace (doc) makes it easy to update all URL references in your database. To see which links will be replaced in your database, use the --dry-run flag: wp search-replace 'http://oldsite.com' 'http://newsite.com' --dry-run When ready to replace the links, run: wp search-replace 'http://oldsite.com' 'http://newsite.com' Wondering what's next? Browse through all of WP-CLI's commands to explore your new world. For more detailed information about creating custom commands, visit the WP-CLI Commands Cookbook. Or, catch up with shell friends to learn about helpful command line utilities. Running Commands Remotely Using an SSH connection WP-CLI accepts an --ssh=[:][@][:][] global parameter for running a command against a remote WordPress install. This argument works similarly to how the SSH connection is parameterized in tools like scp or git. Under the hood, WP-CLI proxies commands to the ssh executable, which then passes them to the WP-CLI installed on the remote machine. The syntax for --ssh=[:][@][:][] is interpreted according to the following rules: The scheme argument defaults to ssh and alternately accepts options for vagrant, docker and docker-compose. If you provide just the host (e.g. wp --ssh=example.com), the user will be inferred from your current system user, the port will be the default SSH port (22) and the path will be the SSH user’s home directory. You can override the user by adding it as a prefix terminated by the at sign (e.g. wp --ssh=admin_user@example.com). You can override the port by adding it as a suffix prepended by a colon (e.g. wp --ssh=example.com:2222). You can override the path by adding it as a suffix (e.g. wp --ssh=example.com~/webapps/production). The path comes immediately after the port, or after the TLD of the host if you didn't explicitly set a port. You can alternatively provide a known alias, stored in ~/.ssh/config (e.g. wp --ssh=rc for the @rc alias). Note: you need to have a copy of WP-CLI installed on the remote server, accessible as wp. Furthermore, --ssh= won’t load your ~/.bash_profile if you have a shell alias defined, or are extending the $PATH environment variable. If this affects you, here’s a more thorough explanation of how you can make wp accessible. Scheme You can utilize the scheme component of the ssh argument to define a shorthand for connecting to local containerized or virtualized machines. The scheme argument is set to ssh by default, but it also accepts vagrant, docker, and docker-compose as alternate options. docker To use Docker, the command is: wp rewrite flush --ssh=docker: The Docker container's name can be found by using the docker ps command. docker-compose For Docker Compose, the command is: wp option get home_url --ssh=docker-compose: The Docker container's name can be located in the docker-compose.yml file. vagrant With Vagrant, you can use the command: wp rewrite flush --ssh=vagrant: Aliases WP-CLI aliases are shortcuts you register in your wp-cli.yml or config.yml to effortlessly run commands against any WordPress install. For instance, when you are working locally, have registered a new rewrite rule and need to flush rewrites inside of your Vagrant-based virtual machine, you can run: # Run the flush command on the development environment $ wp @dev rewrite flush Success: Rewrite rules flushed. Then, once the code goes to production, you can run: # Run the flush command on the production environment $ wp @prod rewrite flush Success: Rewrite rules flushed. You don't need to SSH into machines, change directories, and generally spend a full minute to get to a given WordPress install, you can just let WP-CLI know what machine to work with and it knows how to make the actual connection. It can also easily utilize Vagrant's ssh helper command to figure out the SSH parameters, by piping the WP-CLI command to vagrant ssh using the vagrant scheme like --ssh=vagrant:default where default is the Vagrant machine name/id, or if defined as an alias like the examples below. Some Vagrant boxes ship this by default so you can use WP-CLI from the host machine out-of-the-box. Additionally, alias groups let you register groups of aliases. If you want to run a command against both configured example sites, you can use a group like @both: # Run the update check on both environments $ wp @both core check-update Success: WordPress is at the latest version. Success: WordPress is at the latest version. Aliases can be registered in your project’s wp-cli.yml file, or your user’s global ~/.wp-cli/config.yml file: @prod: ssh: dev_user@example.com~/webapps/production @dev: ssh: vagrant@192.168.50.10/srv/www/example.dev @local: ssh: vagrant:default @both: - @prod - @dev You can find more information about how to set up your configuration files in the Config section. Running custom commands remotely If you want to run a custom command on a remote server, that custom command needs to be installed on the remote server, but it does not have to be installed on the local machine you're launching wp from. You can use the WP-CLI package manager remotely to install custom commands to remote machines. Example: # The command is not installed on either local or remote machine $ wp db ack Error: 'ack' is not a registered subcommand of 'db'. See 'wp help db'. $ wp @dev db ack Error: 'ack' is not a registered subcommand of 'db'. See 'wp help db'. # To make the command work on the remote machine, we can install it remotely # through the WP-CLI package manager $ wp @dev package install runcommand/db-ack Installing package runcommand/db-ack (dev-master) Updating /home/vagrant/.wp-cli/packages/composer.json to require the package... Using Composer to install the package... --- Loading composer repositories with package information Updating dependencies Resolving dependencies through SAT Dependency resolution completed in 0.311 seconds Analyzed 4726 packages to resolve dependencies Analyzed 162199 rules to resolve dependencies Package operations: 1 install, 0 updates, 0 removals Installs: runcommand/db-ack:dev-master aff8ccc - Installing runcommand/db-ack (dev-master aff8ccc) Writing lock file Generating autoload files --- Success: Package installed. # Now we can run the command remotely, even though it is not installed locally $ wp @dev db ack test_email@example.com wp_users:user_email 9:test_email@example.com Running from Windows Powershell Windows Powershell has a special meaning for the @ symbol, meaning it cannot be used without quoting or escaping. To run commands remotely, you need to use one of the following notation. $ wp `@dev core version $ wp "@dev" core version Making WP-CLI accessible on a remote server Running a command remotely through SSH requires having wp accessible on the $PATH on the remote server. Because SSH connections don’t load ~/.bashrc or ~/.zshrc, you may need to specify a custom $PATH when using wp --ssh=. A few ways to make wp available on the remote server are: Copy WP-CLI binary to $HOME / bin In many Linux distros, $HOME/bin is in the $PATH by default, so a way to make wp accessible is to create a $HOME/bin directory, if it doesn't already exist, and move the WP-CLI binary into $HOME/bin/wp: mkdir -p ~/bin cd ~/bin curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar chmod +x wp-cli.phar mv wp-cli.phar wp If $HOME/bin is not already in your path, then you can define it in your ~/.bashrc file or equivalent for your remote server's specific shell: #.bashrc PATH="$HOME/bin:$PATH" Specify the $PATH in $HOME / .ssh/ environment Another way to achieve this is to specify the $PATH in the remote machine user's ~/.ssh/environment file, provided that that machine's sshd has been configured with PermitUserEnvironment=yes (see OpenSSH documentation). Remote non-interactive shell resolves aliases and runs wp as alias with php Some web hosts are configured very restrictively: They do not allow you to execute your own shell scripts, so everything from method WP-CLI binary fails. sshd is configured with PermitUserEnvironment=no, so customizing ~/.ssh/environment has no effect and fails too. Also using the before_ssh hook on the client machine will not help you, as in all cases you cannot run wp on the remote. The solution: On the remote configure ~/.bashrc like this: Configure the remote non-interactive shell to resolve aliases By default aliases in non-interactive shells are not resolved but you can change that: Somewhere very much on top of your ~/.bashrc (certainly before the first alias definitions) insert: shopt -s expand_aliases Be also sure that there's no mechanism which exits the non-interactive shell too early! If you have something like the following construct on top (e.g. Ubuntu is configured like this) then outcomment or delete all lines of the construct: # If not running interactively, don't do anything [ -z "$PS1" ] && return Alias wp to php which runs the WP-CLI binary Somewhere after the shopt -s expand_aliases line insert: alias wp="php ~/bin/wp" or alias wp="php ~/bin/wp-cli.phar" In other words: You have an alias "wp" which is a one liner where php runs the WP-CLI binary by stating the path to the wp-cli.phar file, wherever it may be, under whatever name it may have. php is allowed on basically any webhost. And the wp-cli.phar file itself must not even have the execute flag set (="can be entirely passive"), as formally the file gets run (=interpreted) by php. Note: An executable ~/bin/wp file (the renamed wp-cli.phar with an execute flag) of course also gets run by the php interpreter eventually. But the invocation in the shell is formally different. And that is what makes the crucial difference here. Using before_ssh hook on client machine Alternatively, in case you cannot make it work from within the server, you can achieve the same effect by hooking into the before_ssh hook, and defining an environment variable with the command you’d like to run: WP_CLI::add_hook( 'before_ssh', function() { $host = WP_CLI\Utils\parse_ssh_url( WP_CLI::get_runner()->config['ssh'], PHP_URL_HOST ); switch( $host ) { case 'example.com': putenv( 'WP_CLI_SSH_PRE_CMD=export PATH=$HOME/bin:$PATH' ); break; } } ); If you put the code above in a pre-ssh.php file, you can load it for your entire environment by requiring it from your ~/.wp-cli/config.yml file: require: - pre-ssh.php Sharing WP-CLI Packages By default, WP-CLI places installed packages in ~/.wp-cli/packages/, a hidden subdirectory of the user’s home directory. Because the home directory is different for each user, this naturally means each system user will have a separate directory of installed packages. If you have multiple active shell users on a server, and want to share installed WP-CLI packages between them, there are a couple of supported ways to do this. WP_CLI_PACKAGES_DIR environment variable To override the directory WP-CLI uses for installed packages, provide a WP_CLI_PACKAGES_DIR environment variable. If you wish for multiple users to share the same packages directory, you can simply provide the same WP_CLI_PACKAGES_DIR environment variable for each user. vim /etc/environment export WP_CLI_PACKAGES_DIR=/usr/local/lib/wp-cli-packages Similarly, you can make sure the directory is only writable by a specific user to make packages available to all users, but only installable by the specific user. It’s worth noting the WP_CLI_PACKAGES_DIR environment variable overrides WP-CLI’s default behavior of loading packages installed in the user’s home directory. If you want to support both, you’ll need to take the second approach. Composer project in a shared directory WP-CLI’s installed packages directory is simply a Composer project under the hood. Given this architecture, you can create your own Composer project in an arbitrary directory, and load it into scope using WP-CLI’s --require= flag. First, create your Composer project. $ mkdir /usr/local/lib/wp-cli-packages $ cd /usr/local/lib/wp-cli-packages $ composer init -n --name=runcommand/wp-cli-packages -s=dev $ composer require wp-cli/server-command Now, once your Composer project has a dependency or two, you can use wp --require= (or the equivalent config.yml statement) to load the packages into WP-CLI. Troubleshooting Guide Before you start to report a new issue on the GitHub repository, make sure to check your local installation, as some settings may typically result in an unexpected outcome of your WP-CLI commands. How do I get more verbose information about my WP-CLI installation? WP-CLI offers the command wp --info, which provides you with a lot of information about your WP-CLI install environment. The output will tell you, what operating system and shell you work on which PHP binary is used to run WP-CLI what version of PHP is used where to find WP-CLI's root directory where to find the vendor directory of WP-CLI which WP-CLI binary you are currently working with (phar path) where WP-CLI packages are stored where to find global and project configuration files for WP-CLI and which version of WP-CLI you use. What should I do, before I start debugging issues? Before you start to debug issues, make sure you are using the latest version of WP-CLI. The latest version may already have solved an issue you experience. The command wp cli update will upgrade your WP-CLI version or confirm you already use the latest version. If the installation hangs, please ensure that you are allowed to connect to GitHub using SSL (port 443) and git (port 9418) for outbound connections. What should I do if the WP-CLI output is different than expected? Before starting to investigate a bug, you should be aware of the factors that can change the default behavior of WP-CLI and how you can check whether they might be at the root of the issue. There are five main subsystems for modifying this default behavior: environment variables, configuration files, WP-CLI packages, wp-config.php file and WordPress extensions (plugins, themes, must-use plugins, drop-ins). Environment Variables The setup of your working environment is a prerequisite for running WP-CLI. You can check your current environment settings with the shell command env. If you want to run WP-CLI remotely using SSH, it is required that the command wp is accessible on the path of the remote server. WP-CLI’s behavior can also be changed at runtime through the use of environment variables: WP_CLI_CACHE_DIR - Directory to store the WP-CLI file cache. Default is ~/.wp-cli/cache/. WP_CLI_CONFIG_PATH - Path to the global config.yml file. Default is ~/.wp-cli/config.yml. WP_CLI_CUSTOM_SHELL - Allows the user to override the default /bin/bash shell used. WP_CLI_DISABLE_AUTO_CHECK_UPDATE - Disable WP-CLI automatic checks for updates. WP_CLI_DOCKER_NO_TTY - Ignore detected tty status and remove the --tty flag from docker exec when running WP-CLI against a docker container via ssh. WP_CLI_DOCKER_NO_INTERACTIVE - Ignore detected tty status and remove the --interactive flag from docker exec when running WP-CLI against a docker container via ssh. WP_CLI_PACKAGES_DIR - Directory to store packages installed through WP-CLI's package management. Default is ~/.wp-cli/packages/. WP_CLI_PHP - PHP binary path to use when overriding the system default (only works for non-Phar installation). WP_CLI_PHP_ARGS - Arguments to pass to the PHP binary when invoking WP-CLI (only works for non-Phar installation). WP_CLI_SSH_PRE_CMD - When using --ssh=, perform a command before WP-CLI calls WP-CLI on the remote server. WP_CLI_SSH_BINARY - When using --ssh=, this is the command that will be called on the remote machine in place of wp. WP_CLI_STRICT_ARGS_MODE - Avoid ambiguity by telling WP-CLI to treat any arguments before the command as global, and after the command as local. WP_CLI_SUPPRESS_GLOBAL_PARAMS - Set to true to skip showing the global parameters at the end of the help screen. This saves screen estate for advanced users. WP_CLI_FORCE_USER_LOGIN - Set to 1 to force the value provided to the --user flag to be interpreted as a login instead of an ID, to get around ambiguous types. WP_CLI_EARLY_REQUIRE - Load one or more custom PHP files (comma-separated) early on in the bootstrap process. WP_CLI_REQUIRE - Load one or more custom PHP files (comma-separated) after the bootstrap process. This has the same effect as --require on the command line or a require mapping in a wp-cli.yml config file. WP_CLI_USER_AGENT - Sets the $_SERVER['HTTP_USER_AGENT'] value that some plugins and tools rely on. To set an environment variable on demand, you can place the environment variable definition before the WP-CLI command you mean to run (e.g. EDITOR=vim wp post edit 1); to overwrite environment variables, use export VARIABLE=value in your ~/.bashrc or ~/.zshrc. WP-CLI Configuration Files Configuration files let you customize the behavior of WP-CLI to adapt it to your personal needs or those of your project. If the configuration file is incorrect or includes unwanted modifications to the default behavior, the output of WP-CLI will most likely reflect the error. Note that a project configuration file can override settings in a global configuration file. Rename or delete your configuration file(s) and compare the result, to find out if a configuration setting might have caused the issue. WP-CLI Packages WP-CLI packages are community-maintained projects built on WP-CLI. They can contain WP-CLI commands, but they can also just extend WP-CLI in some way. While WP-CLI might be perfectly working, a package with errors can cause unexpected results, too. To skip loading all installed packages, use wp --skip-packages. WordPress Configuration File ( wp-config.php) Errors may result from moving or editing wp-config.php beyond what WP-CLI supports. If you get a parse error, check the file encoding of your wp-config.php (UTF-8 without BOM). Make sure that the line require_once(ABSPATH . 'wp-settings.php'); remains in the wp-config.php file and don't modify wp-config.php beyond constant definitions. If you call WordPress functions within wp-config.php, PHP will fail with a fatal error. If you want to use $_SERVER['HTTP_HOST'] in your wp-config.php, you’ll need to set a default value in WP-CLI context: if ( defined( 'WP_CLI' ) && WP_CLI && ! isset( $_SERVER['HTTP_HOST'] ) ) { $_SERVER['HTTP_HOST'] = 'example.com'; } Instead of $_SERVER['document_root'] use dirname( __FILE__ ) or similar. WordPress Extensions WordPress Themes and Plugins might conflict with the loading process of WP-CLI or interfere e.g. by redirecting users. They often make assumptions that are not true in the WP-CLI context, like using the hostname of the current request (which is a concept that does not exist within command-line context). You can bypass single plugins and themes (e.g. --skip-plugins=akismet) or skip them entirely ( wp --skip-plugins --skip-themes). What should I do if WP-CLI reports an error? Common problems are the result of changes in your wp-config.php or web server configuration. The section "Common issues" of the handbook lists the most common error messages and explains how to handle these errors. Try to reproduce the issue in a fresh installation of WordPress with a default Theme (Twenty …) and no plugins installed. If the issue only reproduces in a custom environment, then the issue is a bug in your environment, not WP-CLI. Also make sure to enable WordPress' debugging mode by setting the constant define( 'WP_DEBUG', true ); in your wp-config.php. It may reveal problems with your existing WordPress installation, not caused by WP-CLI. Additionally you can use WP-CLI's global parameter --debug to show all PHP errors and add verbosity to WP-CLI bootstrap. I have checked all above, but still have an issue. Where can I report issues? If you think you’ve found a bug, we’d love to hear from you to get it fixed. Bug reporting for WP-CLI is handled on GitHub. Before you create a new issue, please search existing issues to see if there’s an existing resolution to it. If there isn’t an open or fixed issue for your bug, please follow our guidelines for submitting a bug report to make sure it gets addressed in a timely manner. Providing the summary, steps to reproduce, environmental details, and other specifics identified below will help guarantee you are submitting a full bug report. Please provide us with: a summary of the issue in narrative form, a detailed and specific list of steps to reproduce the issue, details of the environment you're working on, a description, how the bug affects your usage (i.e. expected results compared with actual results), severity level, possible workarounds and relevant diagnostics, such as crash reports, stack traces or debug output. You can find a verbose description of these details of an issue report in the guidelines for submitting a bug report. Verifying WP-CLI Downloads This guide explains how to cryptographically verify the authenticity and integrity of WP-CLI releases after downloading them from the Internet and before installing them on your system. Why verify downloads? Verifying downloads ensures: Authenticity: The file was created and signed by the WP-CLI maintainers Integrity: The file has not been corrupted or tampered with during download Quick verification guide After downloading wp-cli.phar, you can verify it using either GPG signatures or checksums. Method 1: Verify using GPG signature (Recommended) This method provides the strongest security as it verifies both the integrity and authenticity of the download. Step 1: Download the signature file curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.asc For a specific version release, download from GitHub releases (replace X.X.X with the desired version): curl -LO https://github.com/wp-cli/wp-cli/releases/download/vX.X.X/wp-cli-X.X.X.phar curl -LO https://github.com/wp-cli/wp-cli/releases/download/vX.X.X/wp-cli-X.X.X.phar.asc For example, to download version 2.12.0: curl -LO https://github.com/wp-cli/wp-cli/releases/download/v2.12.0/wp-cli-2.12.0.phar curl -LO https://github.com/wp-cli/wp-cli/releases/download/v2.12.0/wp-cli-2.12.0.phar.asc Step 2: Import the WP-CLI release signing key WP-CLI releases are signed with the WP-CLI release signing key. Import the public key: curl -L https://raw.githubusercontent.com/wp-cli/builds/gh-pages/wp-cli.pgp | gpg --import The key fingerprint is: 63AF 7AA1 5067 C056 16FD DD88 A3A2 E8F2 26F0 BC06 You can verify the key fingerprint after importing: gpg --list-keys --with-fingerprint releases@wp-cli.org The output should include: pub rsa2048 2018-05-31 [SC] 63AF 7AA1 5067 C056 16FD DD88 A3A2 E8F2 26F0 BC06 uid [ unknown] WP-CLI Releases sub rsa2048 2018-05-31 [E] Note: Releases prior to v1.5.1 (July 2016) were signed with a different key (fingerprint: 3B91 91CD 3CF1 B5C5 10EE FAF6 AFAA E4A2 85E0 40A8). If you need to verify older releases, import that key from the builds repository README. Step 3: Verify the signature For the latest stable release: gpg --verify wp-cli.phar.asc wp-cli.phar For a specific version: gpg --verify wp-cli-2.12.0.phar.asc wp-cli-2.12.0.phar If the signature is valid, you'll see output similar to: gpg: Signature made {DATE} gpg: using RSA key 63AF7AA15067C05616FDDD88A3A2E8F226F0BC06 gpg: Good signature from "WP-CLI Releases " [unknown] You may see a warning about the key not being trusted: gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. This is expected if you haven't personally verified and signed the WP-CLI key. The important part is seeing "Good signature" which confirms the file was signed by this key and hasn't been modified. Method 2: Verify using checksums If GPG is not available, you can verify the integrity (but not authenticity) using SHA-512 or SHA-256 checksums. Using SHA-512 Download the SHA-512 checksum file: curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.sha512 Or for a specific release (replace X.X.X with your version): curl -LO https://github.com/wp-cli/wp-cli/releases/download/vX.X.X/wp-cli-X.X.X.phar.sha512 Verify the checksum by comparing the output: sha512sum wp-cli.phar cat wp-cli.phar.sha512 The two hashes should match exactly. Using SHA-256 Download the SHA-256 checksum file: curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.sha256 Or for a specific release (replace X.X.X with your version): curl -LO https://github.com/wp-cli/wp-cli/releases/download/vX.X.X/wp-cli-X.X.X.phar.sha256 Verify the checksum by comparing the output: sha256sum wp-cli.phar cat wp-cli.phar.sha256 The two hashes should match exactly. Note: Checksum verification only confirms the file matches the published checksum. It does not verify that the file was created by the WP-CLI maintainers. For complete security, use GPG signature verification. Where to find verification files Latest stable and nightly builds Verification files for the latest stable and nightly builds are available at: https://github.com/wp-cli/builds/tree/gh-pages/phar Available files: wp-cli.phar.asc - GPG signature for latest stable wp-cli.phar.sha512 - SHA-512 checksum for latest stable wp-cli.phar.md5 - MD5 checksum (not recommended for security; MD5 is cryptographically broken and vulnerable to collision attacks) wp-cli-nightly.phar.asc - GPG signature for nightly wp-cli-nightly.phar.sha512 - SHA-512 checksum for nightly Specific version releases For a specific version, verification files are available on the GitHub releases page: Each release includes: wp-cli-X.X.X.phar - The WP-CLI phar file wp-cli-X.X.X.phar.asc - Detached GPG signature wp-cli-X.X.X.phar.gpg - Inline GPG signature wp-cli-X.X.X.phar.sha512 - SHA-512 checksum wp-cli-X.X.X.phar.sha256 - SHA-256 checksum wp-cli-X.X.X.phar.md5 - MD5 checksum (not recommended for security; MD5 is cryptographically broken and vulnerable to collision attacks) Complete installation example with verification Here's a complete example of downloading, verifying, and installing WP-CLI: # Download WP-CLI curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar # Download signature curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.asc # Import signing key (first time only) curl -L https://raw.githubusercontent.com/wp-cli/builds/gh-pages/wp-cli.pgp | gpg --import # Verify signature gpg --verify wp-cli.phar.asc wp-cli.phar # If verification succeeds, test it works php wp-cli.phar --info # Make executable and move to PATH chmod +x wp-cli.phar sudo mv wp-cli.phar /usr/local/bin/wp # Verify installation wp --info Additional resources WP-CLI Builds Repository - Contains signing keys and latest builds GitHub Releases - All version releases with verification files GNU Privacy Guard - Learn more about GPG Write a check for asserting the value of a given constant One of the check types included in wp doctor is Constant_Definition, or the ability to assert that a given constant is either defined, a specific value, or falsy. The check type is in use by a couple of the default diagnostic checks, and you can use the Constant_Definition check type in your custom doctor.yml configuration file. As an example, here are two checks using Constant_Definition, one which ensures SAVEQUERIES isn't defined and the other which ensures DISALLOW_FILE_MODS is true: constant-savequeries-falsy: check: Constant_Definition options: constant: SAVEQUERIES falsy: true constant-disallow-file-mods-true: check: Constant_Definition options: constant: DISALLOW_FILE_MODS value: true Run together, you might see: $ wp doctor check --config=constant-definition.yml --all +----------------------------------+---------+--------------------------------------------------+ | name | status | message | +----------------------------------+---------+--------------------------------------------------+ | constant-savequeries-falsy | success | Constant 'SAVEQUERIES' is undefined. | | constant-disallow-file-mods-true | success | Constant 'DISALLOW_FILE_MODS' is defined 'true'. | +----------------------------------+---------+--------------------------------------------------+ The Constant_Definition check type accepts the following options: 'constant': Name of the constant. 'defined': Constant is expected to be defined. 'value': Constant is expected to be a specific value. 'falsy': Constant is expected to be undefined or falsy. Write a check for asserting the value of a given option One of the check types included in wp doctor is Option_Value, or the ability to assert that a given option is a specific value. The check type is in use by a couple of the default diagnostic checks, and you can use the Option_Value check type in your custom doctor.yml configuration file. As an example, here are two checks using Option_Value, one which ensures the blog is public and the other which ensures users can't register: option-blog-public: check: Option_Value options: option: blog_public value: 1 option-users-can-register: check: Option_Value options: option: users_can_register value: 0 Run together, you might see: $ wp doctor check --config=option-value.yml --all +---------------------------+---------+-------------------------------------------------+ | name | status | message | +---------------------------+---------+-------------------------------------------------+ | option-blog-public | error | Site is private but expected to be public. | | option-users-can-register | success | Option 'users_can_register' is '0' as expected. | +---------------------------+---------+-------------------------------------------------+ The Option_Value check type accepts the following options: 'option': Name of the option. 'value': Option is expected to be a specific value. Write a check for verifying contents of WordPress files One of the check types included in wp doctor is File_Contents, or the ability to check all or a selection of WordPress files for a given regex pattern. The check type is in use by a couple of the default diagnostic checks, and you can use the File_Contents check type in your custom doctor.yml configuration file. The File_Contents check type is the most efficient way to check WordPress files because it only iterates the filesystem once, regardless of how many file checks you've registered. As an example, here are two checks using File_Contents, one which ensures sessions aren't used by plugins and the other which ensures $_SERVER['SERVER_NAME'] isn't used in wp-config.php: file-sessions: check: File_Contents options: regex: .*(session_start|\$_SESSION).* only_wp_content: true file-server-name-wp-config: check: File_Contents options: regex: define\(.+WP_(HOME|SITEURL).+\$_SERVER.+SERVER_NAME path: wp-config.php Run together, you might see: $ wp doctor check --config=file-contents.yml --all +----------------------------+---------+-----------------------------------------------------------------------------------------+ | name | status | message | +----------------------------+---------+-----------------------------------------------------------------------------------------+ | file-sessions | success | All 'php' files passed check for '.*(session_start|\$_SESSION).*'. | | file-server-name-wp-config | success | All 'php' files passed check for 'define\(.+WP_(HOME|SITEURL).+\$_SERVER.+SERVER_NAME'. | +----------------------------+---------+-----------------------------------------------------------------------------------------+ The File_Contents check type accepts the following options: 'regex': Regex pattern to check against each file's contents. 'extension'. File extension to check. Defaults to 'php'. Separate multiple file extensions with a '|'. 'path'. Check a specific file path. Value should be relative to ABSPATH (e.g. 'wp-content' or 'wp-config.php'). 'only_wp_content': Only check the wp-content directory. Write a custom check to perform an arbitrary assertion Because wp doctor checks are built on top of a foundational abstraction, it's relatively straightforward for you to write your own custom check. The basic requirement is that you create a class extending runcommand\Doctor\Checks\Check that implements a run() method. The run() must set a status and message based on whatever procedural logic As an example, here's an annotated custom check to assert Akismet is activated with a valid API key: set_when( 'after_wp_load' ); } public function run() { // If the Akismet isn't activated, bail early. if ( ! class_exists( 'Akismet' ) ) { $this->set_status( 'error' ); $this->set_message( "Akismet doesn't appear to be activated." ); return; } // Verify that the API exists. $api_key = Akismet::get_api_key(); if ( empty( $api_key ) ) { $this->set_status( 'error' ); $this->set_message( 'API key is missing.' ); return; } // Verify that the API key is valid. $verification = Akismet::verify_key( $api_key ); if ( 'failed' === $verification ) { $this->set_status( 'error' ); $this->set_message( 'API key verification failed.' ); return; } // Everything looks good, so report a success. $this->set_status( 'success' ); $this->set_message( 'Akismet is activated with a verified API key.' ); } } If the class were placed in an akismet-activated.php file, you could register it with: plugin-akismet-activated: class: Akismet_Activated require: akismet-activated.php Then, run the config file: $ wp doctor check plugin-akismet-activated --config=doctor.yml +--------------------------+--------+---------------------+ | name | status | message | +--------------------------+--------+---------------------+ | plugin-akismet-activated | error | API key is missing. | +--------------------------+--------+---------------------+ Writing Behat Tests for WP-CLI WP-CLI uses Behat for functional testing. This guide will help you understand how to write and run Behat tests for WP-CLI commands and packages. Introduction What are Behat Tests? Behat is a behavior-driven development (BDD) framework for PHP. In WP-CLI, Behat tests are used as functional tests that execute the entire command and verify it works as expected. Unlike unit tests that test individual functions in isolation, functional tests ensure commands work correctly from end to end. Why Behat Tests Matter Stability between releases is an important contract WP-CLI makes with its users. Behat tests help ensure: Commands work as documented Changes don't break existing functionality Edge cases are handled properly The entire command execution flow is validated Tests are required for most pull requests. If it's a new feature, tests are needed. If it's fixing a bug, tests are needed to prevent regression. Getting Started Every WP-CLI repository with commands has a features/ directory containing one or more Gherkin-formatted *.feature files. Writing Your First Test Basic Test Structure Here's a simple example: Feature: Manage WordPress options Scenario: Read an individual option Given a WP install When I run `wp option get home` Then STDOUT should be: """ https://example.com """ This test follows the Gherkin syntax with three key components: Feature: - Documents the scope of the file. This should describe the overall functionality being tested. Scenario: - Describes a specific test case. Each feature file can have multiple scenarios. Given - Sets up the initial environment for the test (preconditions). When - Causes an event to occur (the action being tested). Then - Asserts what's expected after the event is complete (verification). In a more human-friendly form: I have a WordPress installation. When I run wp option get home, then the output from the command should be 'https://example.com'. Your First Test Let's write a test for a hypothetical command that displays a greeting: Create a file features/greeting.feature: Feature: Test greeting command Scenario: Display a greeting Given an empty directory When I run `wp greeting hello` Then STDOUT should contain: """ Hello """ And the return code should be 0 This test: Starts with an empty directory Runs your command Verifies the output contains "Hello" Verifies the command succeeded (exit code 0) Common Step Definitions WP-CLI provides many pre-built step definitions. Here are the most commonly used: Given (Setup) Given an empty directory - Creates a clean working directory Given a WP install - Installs a fresh WordPress instance Given a WP multisite install - Installs WordPress in multisite mode Given a database - Creates an empty database When (Actions) When I run 'wp command' - Execute a command and expect success When I try 'wp command' - Execute a command (success or failure) When I launch in the background 'wp command' - Run command in background Then (Assertions) Then STDOUT should be: - Exact match of output Then STDOUT should contain: - Output contains text Then STDOUT should not contain: - Output doesn't contain text Then the return code should be 0 - Command succeeded Then the return code should be 1 - Command failed Then STDOUT should be empty - No output produced See the Behat Steps reference for a complete list of available steps. Running Tests Setting Up Your Test Environment Before running tests, you need to set up a test database. The composer behat script will automatically detect database availability and fall back to SQLite if there is no running database, making it easy to get started without any setup. You have two options: Option 1: SQLite (Recommended for simplicity) The easiest way to run tests is with SQLite, which requires no database setup: WP_CLI_TEST_DBTYPE=sqlite composer behat Or simply run composer behat and let it automatically use SQLite if no database is configured. Option 2: MySQL/MariaDB If you prefer to use MySQL or MariaDB, run the setup script to create the test database: composer prepare-tests This creates a MySQL user wp_cli_test with password password1 that has full privileges on the wp_cli_test database. Note: MySQL 8.0 changed the default authentication plugin. If you have connection issues, see this blog post for solutions. Running the Test Suite Run all tests: composer behat Run tests for a specific feature file: composer behat -- features/option.feature Run a specific scenario (by line number): composer behat -- features/option.feature:10 Run with verbose output: composer behat -- features/option.feature --format pretty Re-run only failed tests: composer behat-rerun Discovering Available Steps To see all available step definitions: composer behat -- --definitions l This is helpful when writing tests to find the right step for your needs. Advanced Usage Writing Effective Tests Test One Thing at a Time Each scenario should test a single piece of functionality: # Good - tests one specific behavior Scenario: Delete a post Given a WP install And I run `wp post create --post_title='Test' --porcelain` And save STDOUT as {POST_ID} When I run `wp post delete {POST_ID}` Then STDOUT should contain: """ Success: Trashed post """ # Bad - tests multiple unrelated behaviors Scenario: Create, update, and delete a post # Too much in one test... Use Descriptive Scenario Names Make it clear what's being tested: # Good Scenario: Fail gracefully when post doesn't exist # Bad Scenario: Test delete Test Both Success and Failure Don't just test the happy path: Scenario: Successfully create a post Given a WP install When I run `wp post create --post_title='Test'` Then the return code should be 0 Scenario: Fail when required argument is missing Given a WP install When I try `wp post create` Then the return code should be 1 And STDERR should contain: """ Error """ Using Variables Store command output in variables for later use: Scenario: Create and then fetch a post Given a WP install When I run `wp post create --post_title='Test Post' --porcelain` Then save STDOUT as {POST_ID} When I run `wp post get {POST_ID} --field=title` Then STDOUT should be: """ Test Post """ Testing Tables and Structured Output Testing Table Output Scenario: List posts in table format Given a WP install And I run `wp post create --post_title='First'` And I run `wp post create --post_title='Second'` When I run `wp post list --fields=ID,post_title` Then STDOUT should be a table containing rows: | ID | post_title | | 1 | First | | 2 | Second | Testing JSON Output Scenario: List posts in JSON format Given a WP install And I run `wp post create --post_title='Test' --porcelain` When I run `wp post list --format=json` Then STDOUT should be JSON containing: """ [{"post_title":"Test"}] """ Testing CSV Output Scenario: Export posts as CSV Given a WP install When I run `wp post list --format=csv` Then STDOUT should be CSV containing: | ID | post_title | post_status | | 1 | Test | publish | Testing Files and Directories Scenario: Create a plugin file Given a WP install When I run `wp scaffold plugin my-plugin` Then the my-plugin/my-plugin.php file should exist And the my-plugin/my-plugin.php file should contain: """ Plugin Name: My Plugin """ Background Sections Use Background: to run common setup for all scenarios: Feature: Post management Background: Given a WP install And I run `wp post create --post_title='Test'` Scenario: Update post title When I run `wp post update 1 --post_title='Updated'` Then STDOUT should contain: """ Success """ Scenario: Delete post When I run `wp post delete 1` Then STDOUT should contain: """ Success """ Testing Error Messages Use When I try instead of When I run to allow commands to fail: Scenario: Error when plugin doesn't exist Given a WP install When I try `wp plugin activate non-existent-plugin` Then the return code should be 1 And STDERR should contain: """ Error: The 'non-existent-plugin' plugin could not be found. """ Using Scenario Outlines Test multiple inputs with a single scenario: Scenario Outline: Create posts with different titles Given a WP install When I run `wp post create --post_title=''` Then STDOUT should contain: """ Success """ Examples: | title | | Simple Title | | Title's Quotes | | UTF-8: 日本語 | Writing Custom Steps For advanced use cases, you might need custom step definitions. These are defined in PHP in your package's test bootstrap file. Example custom step definition: /** * @Given a custom configuration file */ public function aCustomConfigurationFile() { $config = <<<EOT custom_setting: value another_setting: 123 EOT; $this->proc( "echo '{$config}' > wp-cli.yml" )->run_check(); } Custom steps should: Have descriptive docblock annotations Follow Gherkin conventions (Given/When/Then) Be reusable across multiple tests Use existing WP-CLI test framework methods For most packages, the built-in step definitions in wp-cli/wp-cli-tests are sufficient. Only create custom steps when necessary. Testing with Different PHP and WordPress Versions WP-CLI tests run against multiple PHP and WordPress versions in CI. Your tests should work across these versions. Keep in mind: WordPress behavior may differ between versions Use version-appropriate expectations Avoid testing WordPress core bugs (test your command, not WordPress) Database Credentials By default, MySQL/MariaDB tests use: Database: wp_cli_test Username: wp_cli_test Password: password1 Host: localhost Override these with environment variables from wp-cli/wp-cli-tests: WP_CLI_TEST_DBNAME WP_CLI_TEST_DBUSER WP_CLI_TEST_DBPASS WP_CLI_TEST_DBHOST Alternatively, use SQLite by setting WP_CLI_TEST_DBTYPE=sqlite to avoid database configuration entirely. Best Practices Do's Test command behavior, not implementation - Tests should verify what the command does, not how it does it Make tests independent - Each test should run successfully in isolation Use meaningful test data - Use realistic post titles, user names, etc. Test edge cases - Empty strings, special characters, large numbers Keep scenarios focused - One logical test per scenario Use Given steps for setup - Don't mix setup and testing Test error conditions - Verify your command fails appropriately Don'ts Don't test WordPress core functionality - Test your command, not WordPress Don't make tests depend on each other - Each scenario should stand alone Don't use hard-coded IDs - Use variables or porcelain output Don't skip error testing - Failed commands are important to test Don't test implementation details - Focus on user-facing behavior Don't forget to test help text - Include scenarios for wp help your-command Scaffolding Tests for New Packages When creating a new WP-CLI package, use wp scaffold package-tests to generate the testing infrastructure: wp scaffold package my-package cd my-package wp scaffold package-tests . This creates: .github/workflows/testing.yml - GitHub Actions workflow features/ - Directory for your feature files features/load-wp-cli.feature - Basic test to verify WP-CLI loads Other testing infrastructure files See the scaffold-package-command documentation for more details. Troubleshooting Tests Fail to Connect to Database If you see database connection errors: Try using SQLite instead: WP_CLI_TEST_DBTYPE=sqlite composer behat If using MySQL, verify it's running: mysql -u wp_cli_test -ppassword1 wp_cli_test Check your MySQL version (MySQL 8.0 has authentication changes) Verify the database was created: composer prepare-tests Tests Pass Locally But Fail in CI Common causes: Different PHP versions (check your CI matrix) Different WordPress versions (check your test matrix) Timing issues (some commands need longer to complete) Database collation differences Command Not Found Errors If your command isn't found during tests: Verify your composer.json properly declares the command Check that composer install ran successfully Ensure your command class is properly autoloaded Slow Tests If tests are slow: Minimize database operations Use Given an empty directory instead of Given a WP install when possible Avoid unnecessary plugin installations Use --porcelain to reduce output parsing Additional Resources Behat Steps Reference - Complete list of available steps WP-CLI Test Framework - The underlying test framework Behat Documentation - Official Behat documentation Writing Functional Tests for WP-CLI Packages - Detailed tutorial Pull Request Guidelines - Contributing guidelines Examples from WP-CLI Packages Real-world examples from WP-CLI core packages: Core Command Tests - Examples of testing installation and updates Search-Replace Command Tests - Testing database operations Scaffold Command Tests - Testing file generation Study these to see how different types of commands are tested in practice. Now you're ready to write comprehensive Behat tests for your WP-CLI commands! Remember: good tests make WP-CLI more reliable for everyone.