# Guides

# Guides

* **[Installing](/link/244)** - Recommended and alternative installation mechanisms.
* **[Quick start](/link/246)** - Where to begin after you've installed WP-CLI for the first time.
* **[Running commands remotely](/link/247)** - Learn how to remotely control multiple servers at once. 
* **[Commands cookbook](/link/236)** - The full 101 how commands work, writing your own, and sharing them with the world.
* **[Writing Behat Tests](/link/255)** - How to write and run Behat tests for WP-CLI commands and packages.
* **[Common issues and their fixes](/link/237)** - In case of fire, break glass.
* **[External resources](/link/242)** - Blog posts, slides and videos from users.
* **[Force output to a specific locale](/link/243)** - Localisation issue
* **[Identify a Plugin or Theme Conflict](/link/263)** - Debugging advice
* **[Installing WP-CLI Packages](/link/245)** - Learn how to install packages and use version constraints
* **[Sharing WP-CLI Packages](/link/248)** - Some words about your environment
* **[Troubleshooting Guide](/link/249)** - 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:**

1. Press **Command (⌘) + Space** to open Spotlight Search.
2. Type `Terminal` and press **Enter**.
3. A window with a black or white background and a blinking cursor will appear. That's the terminal!

**On Windows:**

1. Press **Windows key + R** to open the Run dialog.
2. Type `cmd` and press **Enter**.
3. 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](https://git-scm.com/downloads).

**On Linux:**

1. 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](/link/244) 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](#running-wp-cli-inside-vs-outside-the-wordpress-folder)). 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](/link/576)** — Search existing resources, ask a question, or report a reproducible bug.
- **[WP-CLI Handbook](https://make.wordpress.org/cli/handbook/)** — The full reference documentation.
- **[GitHub Issues](https://github.com/wp-cli/wp-cli/issues)** — Report bugs or search for known problems.
- **[WordPress.org Support Forums](https://wordpress.org/support/)** — 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:

1. **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.

2. **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](/link/247) guide for details.

## Learning More

Now that you've got the basics, here are some great next steps:

- **[Common Tasks with WP-CLI](/link/238)** — Practical examples for everyday WordPress management.
- **[Quick Start Guide](/link/246)** — A short walkthrough of WP-CLI with more examples.
- **[Running Commands Remotely](/link/247)** — Manage your live site's server over SSH.
- **[All WP-CLI Commands](https://developer.wordpress.org/cli/commands/)** — The full list of commands available.
- **[External Resources](/link/242)** — 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](/link/240), you can use the `Plugin_Status` check type in your [custom `doctor.yml` configuration file](/link/239).

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](https://github.com/wp-cli/scaffold-package-command)) 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](https://developer.wordpress.org/cli/commands/plugin/install/)) is one such command, as is `wp plugin activate` ([doc](https://developer.wordpress.org/cli/commands/plugin/activate/)). 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](https://developer.wordpress.org/cli/commands/), while also offering a [rich internal API](/link/298) 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](https://packagist.org/?type=wp-cli-package). You can use `wp scaffold package` ([repo](https://github.com/wp-cli/scaffold-package-command)) 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](https://developer.wordpress.org/cli/commands/search-replace/)).
* 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](https://github.com/wp-cli/scaffold-package-command)).
* 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](/link/270).

## Anatomy of a command

WP-CLI supports registering any callable class, function, or closure as a command. `WP_CLI::add_command()` ([doc](/link/514)) 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 <plugin|zip|url>... [--version=<version>] [--force] [--activate] [--activate-network]

In this example, `<plugin|zip|url>...` 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=<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](/link/269) 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:

1. `$name` is the command's name within WP-CLI's namespace (e.g. `plugin install` or `post list`).
2. `$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](https://developer.wordpress.org/cli/commands/eval-file/)).

Given a `simple-command.php` file:

```
<?php
WP_CLI::success( "The script has run!" );
```

Your "command" can be run with `wp eval-file simple-command.php`. If the command doesn't have a dependency on WordPress, or WordPress isn't available, you can use the `--skip-wordpress` flag to avoid loading WordPress. 

### Optional registration arguments

WP-CLI supports two ways of registering optional arguments for your command: through the callable's PHPDoc, or passed as a third `$args` parameter to `WP_CLI::add_command()`.

#### Annotating with PHPDoc

A typical WP-CLI class looks like this:

```
<?php
/**
 * Implements example command.
 */
class Example_Command {

	/**
	 * Prints a greeting.
	 *
	 * ## OPTIONS
	 *
	 * <name>
	 * : The name of the person to greet.
	 *
	 * [--type=<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
	 *
	 * <name>
	 * : The name of the person to greet.
	 *
	 * [--type=<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**:

* `<name>` is a required positional argument. Changing it to `<name>...` would mean the command can accept one or more positional arguments. Changing it to `[<name>]` would mean that the positional argument is optional and finally, changing it to `[<name>...]` would mean that the command can accept multiple optional positional arguments.
* `[--type=<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[=<value>]]` 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[=<plugins>]` which can either skip loading all plugins, or skip a comma-separated list of plugins.
* `[--status=<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 `[--<field>=<value>]`.  So for example:

```
	 * [--<field>=<value>]
	 * : 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=<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 `-<alias>=value` (single-dash) and `--<name>=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=<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=<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](http://michelf.ca/projects/php-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](/link/270).

#### 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](/link/515).

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](/link/269) (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](https://github.com/wp-cli/wp-cli/issues/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](/link/298) this command uses to achieve its goal:

* `WP_CLI::add_command()` ([doc](/link/514)) 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](/link/521)) renders a nicely formatted error message and exits.
* `WP_CLI::launch_self()` ([doc](/link/528)) 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](/link/530)) renders informational output to the end user.
* `WP_CLI\Utils\format_items()` ([doc](/link/538)) 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:

 1. Short description
 2. Synopsis
 3. Long description (OPTIONS, EXAMPLES etc.)
 4. 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](https://github.com/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](/link/269) (usually found at `~/.wp-cli/config.yml`) to require a PHP file:

```yaml
require:
  - ~/.wp-cli/commands.php
```

Then create the file and add your custom commands:

```php
<?php

WP_CLI::add_command( 'hello-world', function () {
    WP_CLI::success( "Hello World!" );
} );
```

Any commands registered in that file will be globally available whenever you run WP-CLI. This is great for personal helper commands that you want available across all your WordPress projects.

You can also pass the `--require=<path>` 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](/link/244#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:

```php
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 `<WordPress 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](https://github.com/wp-cli/wp-cli/blob/main/php/wp-cli.php#L22) out of `wp-config.php`, [it uses regex](https://github.com/wp-cli/wp-cli/blob/main/php/WP_CLI/Runner.php#L324) 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:

```Shell
$ 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](https://developer.wordpress.org/advanced-administration/plugins/mu-plugins/), 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](https://github.com/wp-cli/wp-cli/issues/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:

```bash
# 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:

```bash
$ php -d memory_limit=512M "$(which wp)" package install <package-name>
```

If your PHP process is still running out of memory, try these steps:

1. Restart PHP.
2. Check for additional php.ini files:

```bash
$ 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](https://github.com/wp-cli/wp-cli/pull/973#issuecomment-35842969)

### 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](https://github.com/wp-cli/wp-cli/issues/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`](https://en.wikipedia.org/wiki/Byte_order_mark) 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](https://github.com/wp-cli/search-replace-command/issues/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](https://github.com/wp-cli/wp-cli/issues/785)

### Conflict between global parameters and command arguments

All of the [global parameters](/link/269#global-parameters) (e.g. `--url=<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](https://github.com/wp-cli/wp-cli/pull/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](https://github.com/wp-cli/wp-cli/issues/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](http://php.net/manual/en/migration71.windows-support.php). 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](https://github.com/wp-cli/wp-cli/issues/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="<multi-line 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](https://github.com/wp-cli/wp-cli/issues/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](https://github.com/wp-cli/wp-cli/issues/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:

1. **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.
2. **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](https://xdebug.org/). Here are a few solutions to the problem:

1. Temporarily turn off Xdebug while executing the CLI command: `XDEBUG_MODE=off wp <command> ...`
2. Increase the value of `xdebug.max_nesting_level` in your `php.ini` file. You can learn more about it here - [`xdebug.max-nesting-level`](https://xdebug.org/docs/all_settings#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 <command> ...`

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](/link/234) 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](https://developer.wordpress.org/cli/commands/).
- Read the [Quick Start Guide](/link/246) for a more detailed walkthrough.
- Check out [External Resources](/link/242) for tutorials, blog posts, and videos.


# Customize doctor diagnostic checks

Even though `wp doctor` comes with a [number of default diagnostic checks](/link/240), 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](/link/254) 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](/link/251).
*   `check: File_Contents`: Check all or a selection of WordPress files for a given regex pattern. [Learn more](/link/253).
*   `check: Option_Value`: Assert a given option as a specific value. [Learn more](/link/252).
*   `check: Plugin_Status`: Assert a given plugin as active, installed, or uninstalled. [Learn more](/link/235).

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](/link/254) by extending the `runcommand\Doctor\Checks\Check` class.


# Default doctor diagnostic checks

Although its power comes from its [ability to be customized](/link/239), `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:

<table>

<thead>

<tr>

<th>name</th>

<th>description</th>

</tr>

</thead>

<tbody>

<tr>

<td>autoload-options-size</td>

<td>Warns when autoloaded options size exceeds threshold of 900 kb.</td>

</tr>

<tr>

<td>constant-savequeries-falsy</td>

<td>Confirms expected state of the SAVEQUERIES constant.</td>

</tr>

<tr>

<td>constant-wp-debug-falsy</td>

<td>Confirms expected state of the WP_DEBUG constant.</td>

</tr>

<tr>

<td>core-update</td>

<td>Errors when new WordPress minor release is available; warns for major release.</td>

</tr>

<tr>

<td>core-verify-checksums</td>

<td>Verifies WordPress files against published checksums; errors on failure.</td>

</tr>

<tr>

<td>cron-count</td>

<td>Errors when there's an excess of 50 total cron jobs registered.</td>

</tr>

<tr>

<td>cron-duplicates</td>

<td>Errors when there's an excess of 10 duplicate cron jobs registered.</td>

</tr>

<tr>

<td>file-eval</td>

<td>Checks files on the filesystem for regex pattern `eval\(.*base64_decode\(.*`.</td>

</tr>

<tr>

<td>option-blog-public</td>

<td>Confirms the expected value of the 'blog_public' option.</td>

</tr>

<tr>

<td>plugin-active-count</td>

<td>Warns when there are greater than 80 plugins activated.</td>

</tr>

<tr>

<td>plugin-deactivated</td>

<td>Warns when greater than 40% of plugins are deactivated.</td>

</tr>

<tr>

<td>plugin-update</td>

<td>Warns when there are plugin updates available.</td>

</tr>

<tr>

<td>theme-update</td>

<td>Warns when there are theme updates available.</td>

</tr>

</tbody>

</table>

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](/link/239), you can use `wp doctor list --config=<file>` to view the diagnostic checks listed in the file.


# Doctor Guides

Here are some helpful guides for using `wp doctor`:

* [Default doctor diagnostic checks](/link/240)
* [Customize doctor diagnostic checks](/link/239)
* [Write a custom check to perform an arbitrary assertion](/link/254)
* [Write a check for verifying contents of WordPress files](/link/253)
* [Write a check for asserting the value of a given option](/link/252)
* [Write a check for asserting the value of a given constant](/link/251)
* [Check status of a given plugin](/link/235)


# External Resources

## Blog posts & tutorials

* [scribu: A Command-Line Interface for WordPress](https://scribu.net/wordpress/a-command-line-interface-for-wordpress.html)
* [madewithlove: WP-CLI: WordPress Command Line Tools](https://madewithlove.com/blog/wp-cli-wordpress-command-line-tools/)
* [Mika Epstein: Command Line WP](https://halfelf.org/2012/command-line-wp/)
* [Treehouse Blog: Tame WordPress from the Command Line with wp-cli](https://blog.teamtreehouse.com/tame-wordpress-from-the-command-line-with-wp-cli)
* [Matt Wiebe: Why You Should Develop Plugins With wp-cli](https://mattwiebe.wordpress.com/2014/01/15/why-you-should-develop-plugins-with-wp-cli/)
* [Torque: Using WP CLI to Set Up a Test Version of Your Site](https://torquemag.io/using-wp-cli-to-set-up-a-test-version-of-your-site/)
* [WP Bullet Guides: Useful Bash Scripts Utilizing WP-CLI](https://guides.wp-bullet.com/category/wp-cli/)
* [Delicious Brains: Writing Functional Tests for WP-CLI Packages](https://deliciousbrains.com/writing-functional-tests-wp-cli-packages/) 

## Slides

* [Francesco Laffi: WPday Bologna 2013: wp-cli](https://speakerdeck.com/francescolaffi/wpday-bologna-2013-wp-cli)
* [Michael Bastos: Command Line WordPress Step by Step](https://docs.google.com/presentation/d/1iDYvmM_52ww_iB4aGMAYL_KJ1pnElAVm40c7IrZtyz8/edit)
* [Kelly Dwan: Importing (any!) Content with WP-CLI](https://www.slideshare.net/slideshow/nerds-2014wpcli/39052931/)
* [Jaime Martinez: Simplify your day to day tasks with WP-CLI](https://slid.es/jmslbam/wp-cli-wp-meetup010-25-nov-2013)

## Videos

* [scribu: Command Line Learnings For Make Benefit Glorious Nation Of WordPress](https://wordpress.tv/2013/02/23/cristi-burka-command-line-learnings-for-make-benefit-glorious-nation-of-wordpress/)
* [Mike Schroder: Magical WordPress Management using WP-CLI](https://wordpress.tv/2013/08/06/mike-schroder-magical-wordpress-management-using-wp-cli/)
* [Daniel Bachhuber: WordPress at the Command Line](https://wordpress.tv/2012/08/21/daniel-bachhuber-wordpress-at-the-command-line/)
* [AJ Morris: Managing WordPress from the Command Line](https://wordpress.tv/2016/03/26/aj-morris-using-wp-cli-to-create-your-own-managed-wordpress-hosting/)
* [Shawn Hooper: WP-CLI – Save Time by Managing WordPress from the Command Line](https://wordpress.tv/2015/09/15/shawn-hooper-wp-cli-save-time-managing-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:

    <?php
    WP_CLI::add_wp_hook( 'pre_option_WPLANG', function() {
	    return 'en_US';
    });

You can force the locale to `en_US` with:

    wp --require=force-locale.php

One nice thing about this approach is that you can easily apply it across multiple WP installs [using a config file](/link/269#config-files).

# Installing

## Requirements

WP-CLI requires **PHP 7.2.24 or later**. To check the version of PHP available on your system, run:

```
php --version
```

WP-CLI has no additional requirements beyond those of WordPress itself.

## Recommended installation

The recommended way to install WP-CLI is by downloading the Phar build (archives similar to Java JAR files, [see this article for more detail](http://php.net/manual/en/phar.using.intro.php)), marking it executable, and placing it on your PATH.

First, download [wp-cli.phar](https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar) using `wget` or `curl`. For example:

```
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
```

### Verifying the download (optional but recommended)

Before using the downloaded file, you can verify its authenticity and integrity. See our [guide to verifying WP-CLI downloads](/link/250) for detailed instructions on using GPG signatures or checksums to ensure the file hasn't been tampered with.

Quick verification using GPG:

```
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.asc
curl -L https://raw.githubusercontent.com/wp-cli/builds/gh-pages/wp-cli.pgp | gpg --import
gpg --verify wp-cli.phar.asc wp-cli.phar
```

Then, check if it works:

```
php wp-cli.phar --info
```

To be able to type just `wp`, instead of `php wp-cli.phar`, you need to make the file executable and move it to somewhere in your PATH. For example:

```
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
```

Now try running `wp --info`. If WP-CLI is installed successfully, you'll see output like this:

```
OS:  Linux 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64
Shell:   /usr/bin/zsh
PHP binary:    /usr/bin/php
PHP version:     7.1.12-1+ubuntu16.04.1+deb.sury.org+1
php.ini used:   /etc/php/7.1/cli/php.ini
MySQL binary:
MySQL version:
SQL modes:
WP-CLI root dir:        /home/wp-cli/.wp-cli
WP-CLI packages dir:    /home/wp-cli/.wp-cli/packages/
WP-CLI global config:   /home/wp-cli/.wp-cli/config.yml
WP-CLI project config:
WP-CLI version: 1.5.0
```

Voila! You're now an official WP-CLI user.

Wondering what to do next? Check out the [quick start guide](/link/246) for a short introduction and some example usage.

### Updating WP-CLI

If you have installed WP-CLI using the recommended Phar method, you can update it at any time by running `wp cli update` (although if WP-CLI is owned by root, that may be `sudo wp cli update`). If you installed WP-CLI using the Composer or Git-based installations, see the specific instructions for updating associated with each method below.

When you run `wp cli update`, you'll be prompted to confirm that you wish to update with a message similar to the following:

```
You have version 0.21.1. Would you like to update to 0.23.1? [y/n]
```

After you accept, you should see a success message:

```
Success: Updated WP-CLI to 0.23.1
```

If you're already running the latest version of WP-CLI, you'll see this message:

```
WP-CLI is at the latest version.
```

Want to live life on the edge? Run `wp cli update --nightly` to use the latest nightly build of WP-CLI. The nightly build is more or less stable enough for you to use in your local environment, and always includes the latest and greatest.

For more information about `wp cli update`, including flags and options that can be used, read the full [docs page on the update command](https://developer.wordpress.org/cli/commands/cli/update/).

### Tab completions

#### Bash & Z-Shell

WP-CLI also comes with a tab completion script for _Bash_ and _Z-Shell_. Just download [wp-completion.bash](https://github.com/wp-cli/wp-cli/raw/main/utils/wp-completion.bash) and source it from `~/.bash_profile`:

```
source /FULL/PATH/TO/wp-completion.bash
```

To have this change take effect in your currently active shell, run `source ~/.bash_profile` afterwards.

#### Oh My Zsh

If you're using the _Oh My Zsh_ framework, you can enable the [built-in `wp-cli` plugin](https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins/wp-cli), by adding it to the `plugins=(wp-cli git [...])` line in your `~/.zshrc` file.

Note: the Oh My Zsh plugin comes with the bash completion script included, so it's unnecessary to have both.

To have this change take effect in your currently active shell, run `source ~/.zshrc` afterwards.

#### Fish

If you're using the _Fish_ shell, you can download [wp.fish](https://github.com/wp-cli/wp-cli/raw/main/utils/wp.fish) and move it to `~/.config/fish/completions/wp.fish`. 
Afterwards just type `wp` and press `TAB`, and fish will automatically source `wp.fish`.

### Dash/Alfred workflow

If you're using _Dash_ and _Alfred_, you can add a custom Alfred workflow to look up WP-CLI command information.

#### Setup
 
Open Dash and download the [WP-CLI docset](https://github.com/wp-cli/dash-docset-generator):
Dash › Preferences › Downloads › User Contributions › Search for WP-CLI

Still in Dash, activate the Alfred integration:
Dash › Preferences › Integration › Alfred

#### Usage

Open Alfred and try searching for a specific command. For example, this gives you an overview of the `plugin` command and its subcommands: `wp-cli plugin`

## Alternative installation methods

**Note:** For all Phar-based installation methods below, you can verify the downloaded file's authenticity and integrity. See the [guide to verifying WP-CLI downloads](/link/250) for instructions.

### Installing via Git

If you intend to work on WP-CLI itself, see the [Setting up](/link/568#setting-up) section in [Pull Requests](/link/568).

### Installing nightly via Phar

The "nightly" is the bleeding-edge version of WP-CLI, built straight from the [main branch](https://github.com/wp-cli/wp-cli/commits/main).

Just follow the normal [installation instructions](/link/244), except change the URL to the phar file:

<https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar>

### Installing via Composer

**As part of a project**

Add the following line to your project's `composer.json` file:

    "require" : {
    	"wp-cli/wp-cli-bundle": "*"
    }

To add any additional suggested packages seen in the `wp-cli-bundle` package, such as `psy/psysh`, run: 

```
composer require --dev $(composer suggests --by-package | awk '/wp-cli\/wp-cli-bundle/' RS= | grep -o -P '(?<=- ).*(?=:)')
```

Optionally (if run on a server or for e.g. in a virtual machine locally) you can automate setting up the command and making it available in the users path. Let's assume _Composer_ installed into `/var/www/vendor` (you can get the composer `vendor-dir` config variable specific to your machine via `composer config --list | grep "\[vendor-dir\]"`), we can add the following `scripts`/commands to the `composer.json` file. The second and third line set up [bash completion](https://github.com/wp-cli/wp-cli/blob/main/utils/wp-completion.bash) so we don't have to remember every single command:

    "scripts" : {
    	"post-install-cmd" : [
    		"[[ -f /usr/bin/wp ]] || sudo ln -s /var/www/vendor/wp-cli/wp-cli/bin/wp /usr/bin/wp",
    		"source /var/www/vendor/wp-cli/wp-cli/utils/wp-completion.bash",
        "[[ -f ~/.bash_profile ]] || touch ~/.bash_profile",
    		"source ~/.bash_profile"
    	]
    }

Above script assumes that your current shell is `bash`, which might not be the case for all users. Example for a vagrant box (added to the `scripts`-block):

    cat /etc/passwd | grep $(whoami)
    vagrant:x:1000:1000::/home/vagrant:/bin/bash

In case you got `bash` available and installed for your OS, you can switch dynamically:

    "scripts" : {
    	"post-update-cmd" : [
    		"/bin/bash -c \"[[ -f /usr/local/bin/wp ]] || sudo ln -s /var/www/vendor/wp-cli/wp-cli/bin/wp /usr/bin/wp\"",
    		"/bin/bash -c \"source /var/www/vendor/wp-cli/wp-cli/utils/wp-completion.bash\"",
        "/bin/bash -c \"[[ -f ~/.bash_profile ]] || touch ~/.bash_profile\"",
    		"/bin/bash -c \"source ~/.bash_profile\""
    	]
    }

**As a project**

Needs `php` and `composer` (or `php composer.phar`) set up as console commands.

    composer create-project wp-cli/wp-cli-bundle --prefer-source

Then run `wp-cli-bundle/vendor/wp-cli/wp-cli/bin/wp` or add `wp-cli-bundle/vendor/wp-cli/wp-cli/bin` folder to `PATH` for global `wp` command (on Windows, use `wp-cli/bin/wp.bat` instead).

To update, you'll need to:

    cd wp-cli-bundle
    git pull origin main
    composer install

**Global require**

If you prefer to have PHP tools installed globally via Composer and have something like `~/.composer/vendor/bin` in your PATH (or `C:\Users\you\AppData\Roaming\Composer\vendor\bin` on Windows), you can just run:

    composer global require wp-cli/wp-cli-bundle

To update everything globally, run `composer global update`.

**Installing a specific version**

If you want to install a specific version of WP-CLI then append the version numbers behind the packages

    composer create-project wp-cli/wp-cli-bundle:2.1.0 --no-dev

The version must be in a [format](https://getcomposer.org/doc/04-schema.md#version) that Composer can understand and can be found on [packagist.org](https://packagist.org/packages/wp-cli/wp-cli).

**Installing bleeding-edge**

If you want to install bleeding-edge then use `dev-main`:

    composer create-project wp-cli/wp-cli-bundle:dev-main --no-dev

#### Installing globally as a project

You can specify a custom install path for WP-CLI, like so:

    composer create-project wp-cli/wp-cli-bundle /usr/share/wp-cli --no-dev

Then, just symlink the binary:

    sudo ln -s /usr/share/wp-cli-bundle/vendor/wp-cli/wp-cli/bin /usr/bin/wp

### Installing via Homebrew

    brew install wp-cli

Here's the [formula](https://github.com/Homebrew/homebrew-core/blob/master/Formula/w/wp-cli.rb).

### Installing via Docker

The Docker community maintains [WordPress and WP-CLI images](https://hub.docker.com/_/wordpress/).

To include the WP-CLI image in your own project:

    image: wordpress:cli

### Installing on Windows

Install via [composer as described above](#installing-via-composer) or use the following method.

Make sure you have php installed and [in your path](http://php.net/manual/en/faq.installation.php#faq.installation.addtopath) so you can execute it globally.

Download [wp-cli.phar](https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar) manually and save it to a folder, for example `c:\wp-cli`

Create a file named `wp.bat` in `c:\wp-cli` with the following contents:

    @ECHO OFF
    php "c:/wp-cli/wp-cli.phar" %*

Add `c:\wp-cli` to your path:

    setx path "%path%;c:\wp-cli"

You can now use WP-CLI from anywhere in Windows command line.

#### Using WP-CLI with Git Bash on Windows

If you use [Git Bash](https://gitforwindows.org/) as your shell on Windows, the `wp.bat` wrapper will not work. Instead, create a file named `wp` (no extension) in `c:\wp-cli` with the following contents:

```
#!/usr/bin/env bash

script_path=$(command -v "$0" 2>/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](https://packagist.org/?type=wp-cli-package).

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

```bash
# 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](/link/248).

## 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:

```bash
wp package install <package-name>:<version-constraint>
```

WP-CLI uses Composer's version constraint syntax. Here are some commonly used examples:

```bash
# 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](https://getcomposer.org/doc/articles/versions.md).


# Quick Start

Congratulations! You've [installed WP-CLI](/link/244) 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](https://developer.wordpress.org/cli/commands/plugin/install/) 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](https://developer.wordpress.org/cli/commands/plugin/activate/) 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](/link/514)).

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 <plugin|zip|url>... [--version=<version>] [--force] [--activate] [--activate-network]

In this example, `<plugin|zip|url>...` 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=<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](/link/269) 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**

1. Download the latest version of WordPress with `wp core download` ([doc](https://developer.wordpress.org/cli/commands/core/download/)).

```
$ 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.
```

2. Create a new wp-config.php file with `wp config create` ([doc](https://developer.wordpress.org/cli/commands/config/create/)).

```
$ cd wpclidemo.dev
$ wp config create --dbname=wpclidemo --dbuser=root --prompt=dbpass
1/10 [--dbpass=<dbpass>]:
Success: Generated 'wp-config.php' file.
```

3. Create the database based on wp-config.php with `wp db create` ([doc](https://developer.wordpress.org/cli/commands/db/create/)).

```
$ wp db create
Success: Database created.
```

4. Install WordPress with `wp core install` ([doc](https://developer.wordpress.org/cli/commands/core/install/)).

```
$ 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](https://developer.wordpress.org/cli/commands/plugin/update/)) 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](https://developer.wordpress.org/cli/commands/super-admin/add/)) 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](https://developer.wordpress.org/cli/commands/media/regenerate/)) 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](https://developer.wordpress.org/cli/commands/search-replace/)) 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](https://developer.wordpress.org/cli/commands/) to explore your new world. For more detailed information about creating custom commands, visit the [WP-CLI Commands Cookbook](/link/236). Or, catch up with [shell friends](/link/299) to learn about helpful command line utilities.


# Running Commands Remotely

## Using an SSH connection

WP-CLI accepts an `--ssh=[<scheme>:][<user>@]<host>[:<port>][<path>]` 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=[<scheme>:][<user>@]<host>[:<port>][<path>]` 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=<host>` 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](/link/247#making-wp-cli-accessible-on-a-remote-server) 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:<name>`
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:<name>`
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:<name>`

## 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](https://github.com/Chassis/Chassis/tree/main) 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:

```yaml
@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](/link/269#config-files).

## 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.

```sh
$ 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=<host>`.  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`:

```sh
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:

```sh
#.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](https://en.wikibooks.org/wiki/OpenSSH/Client_Configuration_Files#.7E.2F.ssh.2Fenvironment)).

### 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](#copy-wp-cli-binary-to-home-bin) fails.
- `sshd` is configured with `PermitUserEnvironment=no`, so customizing [~/.ssh/environment](#specify-the-path-in-home-ssh-environment) has no effect and fails too.
- Also [using the before_ssh hook on the client machine](#using-before_ssh-hook-on-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](https://unix.stackexchange.com/questions/425319/how-do-i-execute-a-remote-alias-over-an-ssh/425323#425323) 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:

```php
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:

```yaml
require:
  - pre-ssh.php
```

# Sharing WP-CLI Packages

By default, WP-CLI places [installed packages](https://developer.wordpress.org/cli/commands/package/) 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=<path>` 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=<path/to/autoload>` (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=<ssh>`, perform a command before WP-CLI calls WP-CLI on the remote server.
* `WP_CLI_SSH_BINARY` - When using `--ssh=<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](/link/237)" 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](https://github.com/search?q=org%3Awp-cli+label%3Abug+is%3Aopen+sort%3Aupdated-desc&type=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](/link/560) 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](/link/560).


# 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

```bash
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):

```bash
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:

```bash
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:

```bash
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:

```bash
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 <releases@wp-cli.org>
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](https://github.com/wp-cli/builds/blob/gh-pages/README.md).

#### Step 3: Verify the signature

For the latest stable release:

```bash
gpg --verify wp-cli.phar.asc wp-cli.phar
```

For a specific version:

```bash
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 <releases@wp-cli.org>" [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:

```bash
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):

```bash
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:

```bash
sha512sum wp-cli.phar
cat wp-cli.phar.sha512
```

The two hashes should match exactly.

#### Using SHA-256

Download the SHA-256 checksum file:

```bash
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):

```bash
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:

```bash
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](https://github.com/wp-cli/wp-cli/releases):

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:

```bash
# 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](https://github.com/wp-cli/builds) - Contains signing keys and latest builds
- [GitHub Releases](https://github.com/wp-cli/wp-cli/releases) - All version releases with verification files
- [GNU Privacy Guard](https://gnupg.org/) - 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](/link/240), and you can use the `Constant_Definition` check type in your [custom `doctor.yml` configuration file](/link/239). 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](/link/240), and you can use the `Option_Value` check type in your [custom `doctor.yml` configuration file](/link/239).

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](/link/240), and you can use the `File_Contents` check type in your [custom `doctor.yml` configuration file](/link/239).

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:

    <?php

    /**
     * Ensures Akismet is activated with the appropriate credentials.
     */
    class Akismet_Activated extends runcommand\Doctor\Checks\Check {

    	public function __construct( $options = array() ) {
    		parent::__construct( $options );
    		// Every check is to run on 'after_wp_load' by default.
    		// You could instead use 'before_wp_load' or 'after_wp_config_load'
    		$this->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](https://behat.org/) 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:

```gherkin
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:

1. Create a file `features/greeting.feature`:

```gherkin
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:
1. Starts with an empty directory
2. Runs your command
3. Verifies the output contains "Hello"
4. 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](/link/268) 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:

```bash
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:

```bash
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](https://jonathandesrosiers.com/2019/02/trouble-connecting-to-database-when-using-mysql-8-x/) for solutions.

#### Running the Test Suite

Run all tests:

```bash
composer behat
```

Run tests for a specific feature file:

```bash
composer behat -- features/option.feature
```

Run a specific scenario (by line number):

```bash
composer behat -- features/option.feature:10
```

Run with verbose output:

```bash
composer behat -- features/option.feature --format pretty
```

Re-run only failed tests:

```bash
composer behat-rerun
```

#### Discovering Available Steps

To see all available step definitions:

```bash
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:

```gherkin
# 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:

```gherkin
# 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:

```gherkin
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:

```gherkin
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

```gherkin
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

```gherkin
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

```gherkin
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

```gherkin
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:

```gherkin
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:

```gherkin
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:

```gherkin
Scenario Outline: Create posts with different titles
  Given a WP install

  When I run `wp post create --post_title='<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:**

```php
/**
 * @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](https://github.com/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](https://github.com/wp-cli/wp-cli-tests#the-database-credentials):

- `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:

```bash
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](https://github.com/wp-cli/scaffold-package-command#wp-scaffold-package-tests) for more details.

## Troubleshooting

### Tests Fail to Connect to Database

If you see database connection errors:

1. Try using SQLite instead: `WP_CLI_TEST_DBTYPE=sqlite composer behat`
2. If using MySQL, verify it's running: `mysql -u wp_cli_test -ppassword1 wp_cli_test`
3. Check your MySQL version (MySQL 8.0 has authentication changes)
4. 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:

1. Verify your `composer.json` properly declares the command
2. Check that `composer install` ran successfully
3. 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](/link/268) - Complete list of available steps
- [WP-CLI Test Framework](https://github.com/wp-cli/wp-cli-tests) - The underlying test framework
- [Behat Documentation](https://behat.org/en/latest/) - Official Behat documentation
- [Writing Functional Tests for WP-CLI Packages](https://deliciousbrains.com/writing-functional-tests-wp-cli-packages/) - Detailed tutorial
- [Pull Request Guidelines](/link/568) - Contributing guidelines

## Examples from WP-CLI Packages

Real-world examples from WP-CLI core packages:

- [Core Command Tests](https://github.com/wp-cli/core-command/tree/main/features) - Examples of testing installation and updates
- [Search-Replace Command Tests](https://github.com/wp-cli/search-replace-command/tree/main/features) - Testing database operations
- [Scaffold Command Tests](https://github.com/wp-cli/scaffold-command/tree/main/features) - 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.