How to Create Custom WP-CLI Commands

Recently I’ve been improving my Simple WordPress Crossposting plugin with some custom WP-CLI commands and got a thought in my mind – “why not create a tutorial about that?”

So here it is, below I am going to show you different examples of custom WP-CLI commands with subcommands, arguments and flags.

Creating a Simple WP-CLI Command

Let’s start it simple, so our first custom WP-CLI command is just going to print “Hello world!” or something. The whole process is just about using the WP_CLI::add_command() method.

add_action( 'cli_init', function() {
	
	WP_CLI::add_command( 'mishatest', function( $args ) {
		// print a text line
		WP_CLI::log( 'Hello world, how is it going?' );
	} );
	
} );

Don’t forget to use WP_CLI inside the cli_init action hook, otherwise get ready to get a “Fatal error: Uncaught Error: Class ‘WP_CLI’ not found” message or you can also avoid it by using if ( defined( 'WP_CLI' ) && WP_CLI ) { condition like this:

if( defined( 'WP_CLI' ) && WP_CLI ) {

	WP_CLI::add_command( 'mishatest', function( $args ) {
		// print a text line
		WP_CLI::log( 'Hello world, how is it going?' );
	} );

}

Or:

if( defined( 'WP_CLI' ) && WP_CLI ) {
	require_once __DIR__ . '/inc/class-misha-test-command.php';
}

As a result we get this:

How to create a custom WP CLI command

And now let’s make the things a little bit more interesting.

Output commands (error and success messages)

In the example above we used WP_CLI::log() method to display the text, but there are more available methods:

  • WP_CLI::success(),
  • WP_CLI::error(),
  • WP_CLI::warning().

These methods just add an appropriate colored text string before the initial message like “Success:” and “Error:” (WP_CLI::error() also exits the script, for other commands you will need to additionally use WP_CLI::halt(200)). Example of messages:

WP CLI output functions example

Below we’re also going to use subcommands and arguments to display different kind of messages with the same WP-CLI command.

Class-based Commands

The same command can be written down as a PHP class.

class Misha_Test_Command {
	public function __invoke( $args ) {
		WP_CLI::log( 'Hello world, how is it going?' );
	}
}

add_action( 'cli_init', function() {
	WP_CLI::add_command( 'mishatest', 'Misha_Test_Command' );
	// or:
	// $instance = new Misha_Test_Command();
	// WP_CLI::add_command( 'mishatest', $instance );
} );

We are using the __invoke() method here in order to do something when the initial command is being run. Or we can create other public methods and they will automatically become subcommands.

Creating subcommands

As I’ve already mentioned before, in order to create a subcommand, we just need to add another public method to the PHP class of our command. In the example below we also have a protected method get_message() and when you try to run wp mishatest get_message you will get an error “Error: ‘get_message’ is not a registered subcommand of ‘mishatest’.

Let’s take a look:

class Misha_Test_Command {
    
	protected function get_message() {
		return 'Hello world, how is it going?';
	}
	
	// wp mishatest success
	public function success() {
        WP_CLI::success( $this->get_message() );
	}
	
	// wp mishatest error
	public function error() {
        WP_CLI::error( $this->get_message() );
	}
	
	// wp mishatest warning
	public function warning() {
        WP_CLI::warning( $this->get_message() );
	}
	
	// wp mishatest log
	public function log() {
        WP_CLI::log( $this->get_message() );
	}
}

WP_CLI::add_command( 'mishatest', 'Misha_Test_Command' );

You don’t forget about cli_init hook or defined( 'WP_CLI' ) && WP_CLI condition here, right?

And the result in my MacOS terminal:

WP-CLI positional arguments examples

Command Arguments

Now let’s try to do the same kind of things not with subcommands but with arguments.

Positional arguments

Positional arguments are just what we type after the command. For example if we take a look at the core WordPress WP-CLI command, for example wp plugin install hello, we can see here that “hello” is the positional argument which provides the name of the plugin we’re going to install.

Returning back to our simple example we can use a positional argument to provide a message type we’re going to print, for example wp mishatest success.

WP_CLI::add_command( 'mishatest', function( $args ) {

	// define the message type
	$message_type = ! empty( $args[0] ) ? $args[0] : 'log';

	switch( $message_type ) {
		case 'success' : {
			WP_CLI::success( 'Hello world, how is it going?' );
			break;
		}
		case 'error' : {
			WP_CLI::error( 'Hello world, how is it going?' );
			break;
		}
		case 'warning' : {
			WP_CLI::warning( 'Hello world, how is it going?' );
			break;
		}
		case 'log' : {
			WP_CLI::log( 'Hello world, how is it going?' );
			break;
		}
	}

} );

Here we go:

WP-CLI positional arguments examples

Flags (associative arguments)

And now let’s change our command a little bit, so it is going to look that way: wp mishatest --message_type=success.

WP_CLI::add_command( 'mishatest', function( $args, $flags ) {

	// merge the actual flag values with the default values
	$flags = wp_parse_args(
		$flags,
		array(
			'message_type' => 'log',
		)
	);

	switch( $flags[ 'message_type' ] ) {
		case 'success' : {
			WP_CLI::success( 'Hello world, how is it going?' );
			break;
		}
		case 'error' : {
			WP_CLI::error( 'Hello world, how is it going?' );
			break;
		}
		case 'warning' : {
			WP_CLI::warning( 'Hello world, how is it going?' );
			break;
		}
		case 'log' : {
			WP_CLI::log( 'Hello world, how is it going?' );
			break;
		}
	}

} );

Examples of commands:

WP-CLI flags example

Commands with progress bar

Displaying text messages is super-fun, but let’s do something more meaningful. For example let’s generate posts with the help of an external API (it can be AI by the way).

class Misha_Dino_Command {
	// this method can not be used as a subcommand because it is a protected method
	protected function get_title() {
		// right now we're getting a random dino name via API
		$res = wp_remote_get(
			add_query_arg(
				array(
					'format' => 'text',
					'paragraphs' => 1,
					'words' => 1,
				),
				'https://dinoipsum.com/api/'
			)
		);
		if( 'OK' === wp_remote_retrieve_response_message( $res ) ) {
			// return a randomly generated dino name
			return wp_remote_retrieve_body( $res );
		} else {
			// return an error and exit the script
			WP_CLI::error( 'Oops... something is wrong with the dino name!' );
		}
	}
	// generate posts
	public function generate( $args, $flags ) {

		$flags = wp_parse_args( $flags, array( 'count' => 5 ) );

		$progress = \WP_CLI\Utils\make_progress_bar( 'Generating Posts', $flags[ 'count' ] );

		for( $i = 0; $i < $flags[ 'count' ]; $i++ ) {

			wp_insert_post( array(
				'post_title'  => $this->get_title(),
				'post_status' => 'publish',
				'post_type' => 'post',
			) );

			$progress->tick();
		}

		$progress->finish();

		WP_CLI::success( $flags[ 'count' ] . ' dinosaurus generated!' );
	}
}

add_action( 'cli_init', function() {

	WP_CLI::add_command( 'mishatest', 'Misha_Dino_Command' );

} );

Running the command:

WP-CLI progress bar example

Generated posts in WordPress admin:

Randomly generated WordPress posts with WP-CLI

Help documentation for a command

The last but not the least, let’s provide a help documentation to our command (to be more exact – subcommand) which will be available just by typing --help flag at the end.

There are actually some standards to that, you can find them in the official WordPress documentation here.

/**
 * Generates dinosaurus (posts).
 *
 * ## OPTIONS
 *
 * [--count=<count>]
 * : How many posts to generate in a single run.
 * ---
 * default: 5
 * ---
 *
 * ## EXAMPLES
 *
 *  # Generate 50 posts.
 *  $ wp mishatest generate --count=50
 *  Success: 50 dinosaurus generated!
 */
public function generate( $args, $flags ) {
WP-CLI help documentation of a command

What else would you like to learn about WP-CLI? Let me know in the comments.

Misha Rudrastyh

Misha Rudrastyh

Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.

Need some developer help? Contact me

Follow me on X