WP-CLI The command line interface for WordPress

Version 0.23.1 released

by danielbachhuber, on 28 Apr 2016

WP-CLI v0.23.1 is a compatibility release for those who’ve installed WP-CLI with Composer and require a minimum-stability. See #2664 for related conversation.

If you’re using the Phar distribution of WP-CLI, you should wp cli update, but don’t need to prioritize it, as the release doesn’t change WP-CLI’s behavior in any significant way.

Contributors to this release: danielbachhuber

You can browse the full list of resolved issues on GitHub.

RESTful WP-CLI - What I've been hacking on

by danielbachhuber, on 14 Apr 2016

Let me just say — Thursday, February 4th was pretty darn demoralizing. I spent a huge amount of time in January towards the WP REST API in preparation for what I wanted to do on the command line, and a lot of momentum / inspiration / general good feelings were destroyed in that meeting. As such, I spent much of February and March working on WP-CLI features unrelated to the WP REST API (e.g. package management).

But, I’m back in the saddle. Because I’m 2/3 of the way through one of those fancy WP REST API + React WordPress applications, I’m running into dozens of ways I want to be able to make WordPress more efficiently. And of course, this means doing it on the command line.

Before we proceed: most of the, if not all, RESTful WP-CLI features have required under the hood changes to WP-CLI. You’ll want to wp cli update --nightly to play with this new functionality locally. Once you’ve done so, you can wp package install danielbachhuber/wp-rest-cli to install the latest.

Use --debug and --debug=rest to profile your REST endpoints

REST APIs are all about speed. Milliseconds matter, and every one you manage to shave off will have a real world impact on user experience.

To make it much, much easier to understand how many queries your endpoint is performing, and how long they take, I’ve added some lightweight profiling to RESTful WP-CLI.

Use --debug to get a summary of your queries for any command.

$ wp rest post list --debug
Debug (rest): REST command executed 7 queries in 0.001954 seconds. Use --debug=rest to see all queries. (1.446s)
| id | title                       |
| 1  | {"rendered":"Hello world!"} |

Use --debug=rest to get the full list of queries executed.

$ wp rest post list --fields=id,title --debug=rest
Debug: REST command executed 7 queries in 0.001696 seconds. Ordered by slowness, the queries are:
  - 0.000291 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts
  - SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  WHERE 1=1  AND wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish')  ORDER BY wp_posts.post_date DESC LIMIT 0, 10
  - 0.000257 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, WP_Query->set_found_posts
  - 0.000256 seconds
  - WP_REST_Posts_Controller->get_items, WP_REST_Posts_Controller->prepare_item_for_response, setup_postdata, WP_Query->setup_postdata, get_userdata, get_user_by, WP_User::get_data_by
  - SELECT * FROM wp_users WHERE ID = '1'
  - 0.000244 seconds
  - WP_REST_Posts_Controller->get_items, WP_REST_Posts_Controller->prepare_item_for_response, setup_postdata, WP_Query->setup_postdata, get_userdata, get_user_by, WP_User->init, WP_User->for_blog, WP_User->_init_caps, get_user_meta, get_metadata, update_meta_cache
  - SELECT user_id, meta_key, meta_value FROM wp_usermeta WHERE user_id IN (1) ORDER BY umeta_id ASC
  - 0.000233 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, _prime_post_caches
  - SELECT wp_posts.* FROM wp_posts WHERE ID IN (1)
  - 0.000209 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, _prime_post_caches, update_post_caches, update_object_term_cache, wp_get_object_terms
  - SELECT t.*, tt.*, tr.object_id FROM wp_terms AS t INNER JOIN wp_term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN wp_term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id  WHERE tt.taxonomy IN ('category', 'post_tag', 'post_format') AND tr.object_id IN (1) ORDER BY t.name ASC
  - 0.000206 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, _prime_post_caches, update_post_caches, update_postmeta_cache, update_meta_cache
  - SELECT post_id, meta_key, meta_value FROM wp_postmeta WHERE post_id IN (1) ORDER BY meta_id ASC
| id | title                       |
| 1  | {"rendered":"Hello world!"} |

Profiling works for any CRUD operation.

$ wp rest post create --title="Test post" --user=daniel --debug
Debug (rest): REST command executed 28 queries in 0.023962 seconds. Use --debug=rest to see all queries. (1.777s)
Success: Created post.
$ wp rest post update 3 --content="Foo bar" --user=daniel --debug
Debug (rest): REST command executed 31 queries in 0.023309 seconds. Use --debug=rest to see all queries. (1.634s)
Success: Updated post.

Hopefully this feature becomes an invaluable part of your REST endpoint development process, as it has mine. Hit me with feedback on its GitHub issue.

Use wp rest * edit to edit a resource in your system editor

Most people probably don’t know this, but you can use wp post edit <id> to edit post content in your system editor (e.g. vim). Now, with wp rest * edit, you can edit any REST resource in your system editor.

$ wp rest post edit 3 --user=daniel

When you run wp rest * edit, RESTful WP-CLI fetches the resource, transforms it into a YAML document, and puts it in your system editor:

date: 2016-04-14T14:02:57
date_gmt: null
status: draft
  raw: Test post
  rendered: Test post
  raw: Foo bar
  rendered: |
        <p>Foo bar</p>
  rendered: |
        <p>Foo bar</p>
author: 1
featured_media: 0
comment_status: open
ping_status: open
sticky: false
format: standard
  - 1
tags: [ ]

If you make changes to any of the fields, then the command sends it back to WordPress (through the WP REST API) to update.

On WordPress installs that support Basic Auth, editing also works over HTTP:

$ wp --http=http://daniel:daniel@wordpress-develop.dev rest post edit 1

Et, voila.

Get involved!

I’d love your input on the dozens of ideas I have for a more RESTful WP-CLI:

  • Render the help docs in formats like API Blueprint and Swagger [#36]
  • Introduce wp rest * generate to generate mock data in the format your application expects [#55].
  • Introduce wp rest * diff to be able to diff the state of two different WordPresses, a la Dictator [#56].
  • Figure out an elegant aliases implementation, so --http=http://daniel:daniel@wordpress-develop.dev becomes @wpdev [#2039]

And I want to hear your ideas too! As well as any feedback, questions, or violent dissent. Let’s chat on GitHub.

Version 0.23.0 released

by danielbachhuber, on 22 Mar 2016

It’s hard to believe the last WP-CLI release was only two months ago because this is the longest release post I’ve ever written. My apologies in advance.

If you don’t make it all of the way through, here’s what you absolutely need to know about WP-CLI v0.23.0:

  • This release includes WordPress 4.5 compatibility. Older WP-CLI versions are incompatible with WordPress 4.5. If you’re planning to use WordPress 4.5 in the future, you’ll need to upgrade before you do.
  • You can now browse available packages with wp package browse, and install them with wp package install. Try wp package install runcommand/db-ack.
  • There are many new tools for registering commands. Ain’t no more need to extend WP_CLI_Command. See the commands cookbook to learn more.
  • All of the wiki pages moved to a documentation portal on the website, including new pages documenting internal APIs you can use in your own commands. Pull requests encouraged.

Now that I’ve given away all of the surprises, let’s get on with the release post.

Oh darn, I forgot two more things:

  • For those using the Phar file, which should be most of you, WP-CLI now automatically checks for updates, and will prompt you to update as applicable. It even works for those using the nightly build. This behavior is configurable, see below.
  • I’ve settled on runcommand as the name for my new WP-CLI venture. Sign up for email updates on the website, or follow @runcommand on Twitter.

And now, the release post.

WordPress 4.5 compatibility

WordPress 4.5 loads yet another file in wp-settings.php. Because WP-CLI has a custom wp-settings-cli.php (background), WP-CLI v0.23.0 is the only release compatible with WordPress 4.5.

Importantly, due to the nature of these changes, WP-CLI versions prior to 0.23.0 will be incompatible with WordPress 4.5.

Inspect the nature of the change in this pull request.

Want to help fix this? Refill your cup of coffee and dive into #34936 on WordPress Trac.

Install community commands from the Package Index

Consider the following situation (#2523):

Using the theme list command without --url parameter shows if a theme is enabled for the network and active in the default site.

If you pass the --url of a site of the network, this command shows if a theme is active in that site.

But I can’t find a way to list which themes are inactive in every site of the network so I can safely disable and delete them, and i’d love to have this feature

If you had to complete this task using the WordPress network admin, it would take you hours, if not days. Simply writing the WP-CLI command for this issue took me only 3 minutes (runcommand/find-unused-themes). WP-CLI is genuinely the fastest interface to manage WordPress.

WP-CLI also generally follows the 80/20 rule when deciding whether to introduce a new feature. If the feature could be useful to most people, then maybe; otherwise, probably not. Those 20% shouldn’t be left with the short stick though, particularly when it comes to the really helpful commands.

Today, I’m proud to reintroduce the Package Index, a directory of community-maintained WP-CLI packages. Browse available packages to install with wp package browse (doc). Once you’ve found the solution you’re looking for, install it with wp package install (doc):

$ wp package install runcommand/find-unused-themes
Installing runcommand/find-unused-themes (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
Analyzed 2223 packages to resolve dependencies
Analyzed 29243 rules to resolve dependencies
 - Installing package
Writing lock file
Generating autoload files
Success: Package installed successfully.
$ wp find-unused-themes
Checking http://wordpress-develop.dev/ for unused themes...
Checking http://wordpress-develop.dev/foo/ for unused themes...
| name           | version |
| default        | 1.7.2   |
| mystore        | 1.0.6   |
| p2             | 1.5.5   |
| twentyeleven   | 2.3     |
| twentyfourteen | 1.6     |
| twentyten      | 2.1     |
| twentythirteen | 1.8     |
| twentytwelve   | 1.9     |

Pretty cool, huh? Consider WP-CLI’s package management to be a beta feature at this time. You may run into one of the open issues, or find a bug that hasn’t been reported yet. If you’re keen to take a deep dive into Composer’s internals, helping to improve wp package would be a great way to start.

Relevant pull requests for wp package include #2442, #2460, #2491, #2512, #2514, #2533, #2534, #2537, #2540, #2543, #2546, #2547, #2555, #2561

Register more commands

Ever wonder why, when writing your own command, you had to extend WP_CLI_Command? Well, you didn’t need to, actually.

In fact, WP_CLI::add_command() (doc) now supports registering any arbitrary callable as a WP-CLI command. For instance, here’s a closure command to reset the passwords of one or more users (runcommand/reset-passwords):

 * Reset passwords for one or more WordPress users.
 * <user>...
 * : Specify one or more user logins or IDs.
$reset_password_command = function( $args ) {
	$fetcher = new \WP_CLI\Fetchers\User;
	$users = $fetcher->get_many( $args );
	foreach( $users as $user ) {
		wp_update_user( array( 'ID' => $user->ID, 'user_pass' => wp_generate_password() ) );
		WP_CLI::log( "Reset password for {$user->user_login}." );
	WP_CLI::success( 'Passwords reset.' );
WP_CLI::add_command( 'user reset-passwords', $reset_password_command );

Want to reuse argument definition between commands? You can now register command synopsis as the third argument to WP_CLI::add_command() (runcommand/hook):

WP_CLI::add_command( 'hook', $hook_command, array(
	'shortdesc' => 'List callbacks registered to a given action or filter.',
	'synopsis' => array(
			'name'        => 'hook',
			'type'        => 'positional',
			'description' => 'The key for the action or filter.',
			'name'        => 'format',
			'type'        => 'assoc',
			'description' => 'List callbacks as a table, JSON, or CSV.',
			'optional'    => true,
			'options'     => array( 'table', 'json', 'csv' ),
			'default'     => 'table',
) );

Note the default argument attribute for format. WP-CLI accepts default and options as a part of argument registration to make it easier to process user input within your command. These attributes can be also defined in the callable’s PHPDoc:

 * List callbacks registered to a given action or filter.
 * <hook>
 * : The key for the action or filter.
 * [--format=<format>]
 * : List callbacks as a table, JSON or CSV.
 * ---
 * options:
 *   - table
 *   - json
 *   - csv
 * default: table
 * ---
$hook_command = function( $args, $assoc_args ) {

Check out the commands cookbook for a deep dive into command registration. Relevant pull requests for these improvements to WP_CLI::add_command() include #2373, #2389, #2398, #2409, #2556, #2559.

More, better, easier to find documentation

It’s actually hard to imagine how people got around previously. Here’s what’s changed in documentationland:

  • The wiki has been reincarnated as the documentation portal on the website. I spent time cleaning up the pages as I moved them over; hopefully you find the new commands cookbook quite helpful.
  • Internal APIs you can use in your own commands are now publicly documented. The internal API pages are generated from the codebase, which should greatly help maintenance efforts.
  • On each command page, there’s a “Github issues” link to make it easier to find both open and closed issues for a given command. For instance, here are all of the outstanding issues for wp package and its subcommands. Keep in mind this isn’t necessarily exact, because I didn’t go back through and label all issues of all time. This feature will become more useful as time goes forward.

Relevant pull requests include #2454, #2487, #2494, #2499, #2507, #2513, #2515.

Automatic update checks

For those using WP-CLI as a Phar file, which should be most of you, version 0.23.0 and later will automatically check for updates. If an update is found, you’ll be prompted to install it.

Automatic update checks even work for those using the nightly build! Use wp cli update --nightly to get back on the nightly track after each major release.

By default, the automatic update check will run once a day for users with a writable WP-CLI Phar file. Distribution file or directory not writable? Then the check won’t run. Automatic update checks are triggered when a user, not a script, runs wp help <some-command>. This frequency can be configured with the WP_CLI_AUTO_CHECK_UPDATE_DAYS environment variable, or disabled entirely with the WP_CLI_DISABLE_AUTO_CHECK_UPDATE environment variable.

Relevant pull requests for this feature includes #2536 and #2538.

Everything else in 0.23.0

Command improvements:

  • Better performance for wp (user|post) list --format=count by only fetching the data we need [#2370, #2387].
  • Prevents dupe builds with Travis default settings in wp scaffold plugin-tests [#2377].
  • Generate comments for a specific post with wp comment generate --post_id=<post-id> [#2388].
  • Cleans up files from the prior version when using wp core (update|download) --force [#2382, #2406, #2413, #2432].
  • Adds a timer to individual events in wp cron event run [#2437].
  • Introduces wp term meta for managing term meta [#2444].
  • Adds CSV and JSON output format to wp (theme|plugin) update [#2452].
  • Verifies MD5 hash of downloaded archive file with wp core download [#2461]
  • Entirely avoids loading WordPress in wp core verify-checksums [#2459].
  • Supports emptying term meta with wp site empty [#2506].
  • Adds WP REST API registration args to scaffold (post-type|taxonomy) [#2551].
  • Adds documentation to test-sample.php and bootstrap.php when running wp scaffold plugin-tests [#2577, #2578].

Framework enhancements:

  • Switches WP_CLI::confirm() to automatically lowercase the response, permitting use of Y or y [#2366].
  • Adds (before|after)_wp(_config)_load hooks in WP load process, permitting code injected via --require to make modifications during the bootstrap process [#2375].
  • Adds .editorconfig to project root based on WordPress Coding Standards [#2395].
  • Encodes WP_Error data as JSON in WP_CLI::error_to_string() so that the data is actually human-readable [#2397].
  • Supports custom exit codes in WP_CLI::error() [#2440].
  • Introduces --format=yaml for easily displaying data as YAML [#2453].
  • Supports config deep merging and inheritance [#2496].
  • Updates Composer dependencies [#2554]

Bug fixes across the board:

  • In bin/install-wp-tests.sh, don’t cd in WP_TESTS_DIR before dowloading and setting up wp-tests-config.php [#2371].
  • When using --prompt, now only prompts for the first command in the execution thread. Previously, any use of WP_CLI::run_command() within a command would cause the prompting UX to appear again [#2400].
  • Removes unnecessary exit on premature success for wp theme activate [#2412].
  • Checks if a taxonomy exists before listing its terms [#2414].
  • When trying to update a version of core that doesn’t exist, check the HTTP response code before trying to unzip an invalid archive [#2368].
  • Fixes use of wp server when the PHP binary contains spaces [#2422].
  • Respects --skip_comments flag for wp export, which has been broken for quite a while [#2427]
  • Persists IPTC data in wp media import when missing a title or caption; removes extension from default title [#2438, #2466].
  • Disables check translation updates when updating themes or plugins [#2439].
  • Corrects parameter sequence order when creating a new user on multisite [#2443].
  • Disables automatic colorization when --format=table [#2458].
  • Uses core’s version check API for finding updates, which gives us exact URLs to download offers [#2469].
  • Uses more robust failed download checking in wp cli update [#2488].
  • Runs help early for wp core commands used when core isn’t yet installed [#2497].
  • Fixes formatting of GLOBAL PARAMETERS when command has subcommands [#2516].
  • Properly handles multi-column keys in wp search-replace [#2531].
  • Uses correct path to autoloader when WP-CLI is installed to a parent Composer project [#2550].
  • Properly passes wp plugin search fields to plugins_api() request; adds page parameter [#2570, #2571].
  • Add parent as a potential status in wp theme status [#2573]

Contributors to this release: anantshri, danielbachhuber, edueo, GaryJones, gilbitron, hina, hinoue-work, jacobischwartz, marco-c, markjaquith, markkimsal, mbovel, ottok, rodrigoprimo, sourcerer-mike, staude, szepeviktor, za-creature

You can browse the full list of resolved issues on GitHub.

RESTful WP-CLI - No rest for the weary

by danielbachhuber, on 04 Feb 2016

Like my title? Get the pun? Te he he.

I’m just back from A Day of REST, where I spoke about a more RESTful WP-CLI, and unlocking the potential of the WP REST API at the command line. It was probably the best talk I’ve ever done. You can check out my annotated slides if you haven’t already.

The talk covered the progress I’ve already made, and the hypotheticals on my mind every day when I go for a swim.

wp-rest-cli v0.1.0

Today marks v0.1.0 for wp-rest-cli. This initial release makes WP REST API endpoints available as WP-CLI commands. It does so by:

  • Auto-discovering endpoints from any WordPress site running WordPress 4.4 or higher.
  • Registering WP-CLI commands for the endpoints it understands.

Warning: This project is at a very early stage. Treat it as an experiment, and understand that breaking changes will be made without warning. The sky may also fall on your head.

Here’s how it works:

$ wp rest
usage: wp rest attachment <command>
   or: wp rest category <command>
   or: wp rest comment <command>
   or: wp rest meta <command>
   or: wp rest page <command>
   or: wp rest pages-revision <command>
   or: wp rest post <command>
   or: wp rest posts-revision <command>
   or: wp rest status <command>
   or: wp rest tag <command>
   or: wp rest taxonomy <command>
   or: wp rest type <command>
   or: wp rest user <command>

$ wp --http=demo.wp-api.org rest tag get 65 --format=json
  "id": 65,
  "link": "http://demo.wp-api.org/tag/dolor-in-sunt-placeat-molestiae-ipsam/",
  "name": "Dolor in sunt placeat molestiae ipsam",
  "slug": "dolor-in-sunt-placeat-molestiae-ipsam",
  "taxonomy": "post_tag"

Notice how you can use --http=<domain> to interact with a remote WordPress site. --http=<domain> must be supplied as the second argument to be used. Without it, wp-rest-cli will look for endpoints of a WordPress site in a directory specified by --path=<path> (or the current directory, if --path=<path isn’t supplied).

Using wp-rest-cli requires the latest nightly build of WP-CLI, which you can install with wp cli update --nightly. Once you’ve done so, you can install wp-rest-cli with wp package install danielbachhuber/wp-rest-cli.

Unreleased WP-CLI improvements

Wait, wp package install. What in the?

That’s right, WP-CLI now has package management. Using wp cli update --nightly, you now can:

  • wp package browse to browse packages available for installation.
  • wp package install to install a given package.
  • wp package list to list packages installed locally.
  • wp package uninstall to uninstall a given package.

While I wasn’t planning to dive down this rabbit hole during the Kickstarter project, I was finally inspired on how to finish the feature, and took a couple hours yesterday to do so. It’s amazing how you can be mentally blocked on a problem for literally two years but then, once you’re unblocked, finish it up in a short period of time.

You’ll probably run into one or more bugs with wp package. When you do, please let me know on this issue. If the bugs get too hairy, I may pull the feature from the release and revisit. But, for now, you can much more easily install and use community packages.

wp-rest-cli also makes use of another new feature: register arbitrary functions, closures, and class methods as WP-CLI commands.

For instance, given a closure $hook_command:

$hook_command = function( $args, $assoc_args ) {
    // the meat of the command
WP_CLI::add_command( 'hook', $hook_command, array(
    'shortdesc' => 'List callbacks registered to a given action or filter.',
    'synopsis' => array(
            'name'        => 'hook',
            'type'        => 'positional',
            'description' => 'The key for the action or filter.',
            'name'        => 'format',
            'type'        => 'assoc',
            'description' => 'List callbacks as a table, JSON, or CSV. Default: table.',
            'optional'    => true,
) );

Then, when you run wp hook init, you’ll see:

$ wp hook init
| function                        | priority | accepted_args |
| create_initial_post_types       | 0        | 1             |
| create_initial_taxonomies       | 0        | 1             |
| wp_widgets_init                 | 1        | 1             |
| smilies_init                    | 5        | 1             |
| wp_cron                         | 10       | 1             |
| _show_post_preview              | 10       | 1             |
| rest_api_init                   | 10       | 1             |
| kses_init                       | 10       | 1             |
| wp_schedule_update_checks       | 10       | 1             |
| ms_subdomain_constants          | 10       | 1             |
| maybe_add_existing_user_to_blog | 10       | 1             |
| check_theme_switched            | 99       | 1             |

Want to use this command locally? Update to the nightly, and then run wp package install danielbachhuber/wp-hook-command.

What’s next

Well… I’ve spent a ton of hours over the last month on the WP REST API. 67.03 hours of 83 budgeted, to be precise. Given there doesn’t yet seem to be an end in sight, I may reallocate ~30 hours or so out of the WP-CLI budget for continued involvement with the WP REST API. But, I do need to slow down the pace of my involvement a bit, because it’s not sustainable.

On the wp-rest-cli front, the product problems at the top of my mind are authentication and aliases.

Instead of:

wp --http=demo.wp-api.org --user=daniel:daniel rest tag create

I’d much prefer:

wp @wpapi tag create

In the example preceding, @wpapi is an alias for both the target and authentication.

In this hypothetical universe, aliases would also be injected into the WP-CLI runtime:

$ wp @wpapi
usage: wp @wpapi attachment <command>
   or: wp @wpapi category <command>
   or: wp @wpapi comment <command>
   or: wp @wpapi meta <command>
   or: wp @wpapi page <command>
   or: wp @wpapi pages-revision <command>
   or: wp @wpapi post <command>
   or: wp @wpapi posts-revision <command>
   or: wp @wpapi status <command>
   or: wp @wpapi tag <command>
   or: wp @wpapi taxonomy <command>
   or: wp @wpapi type <command>
   or: wp @wpapi user <command>

There’s a bit of thinking to do, and code to write, to get from here to there, though.

Feeling inspired? I want to hear from you! Particularly if you’ve written custom endpoints I can test against. Please open a GitHub issue with questions, feedback, and violent dissent, or email me directly.

RESTful WP-CLI - The journey begins

by danielbachhuber, on 12 Jan 2016

And so the journey begins. As with most journeys, I have a mixture of emotions: excitement, anticipation, trepidation, and eagerness. Although the destination may be far away, I know I can get there as long as I consistently take steps in the right direction.

Today marks the formal kickoff of my Kickstarter project, “A more RESTFul WP-CLI”. To celebrate the occasion, I’ve launched a project page to capture high-level goals and document my progress along the journey. I’ll keep it updated as I write blog posts every couple or few weeks. Consider these blog posts both a development log and an invitation to participate — I look forward to your comments, issues and pull requests.

For the past month or so, the question at the top of my mind has been: what does it mean to “unlock the potential of the WP REST API at the command line”? Or, even more broadly, why do this project?

These are big questions, and I consider myself fortunate to be able to explore them over the next six months or so. Here’s how I’ve unpacked them so far, in a series of loosely connected ideas:

  • WP-CLI’s goal is to be, quantitatively, the fastest interface for developers to manage WordPress. For anything you want to do with WordPress, using WP-CLI should save you multitudes of time over doing it some other way.
  • WP-CLI and WP REST API both offer CRUD interfaces to WordPress resources. wp post list is more or less GET /wp/v2/posts. But, wp widget list doesn’t yet have an equivalent in WP REST API. We still have a ton of work to do.
  • Building the WP REST API has been, and will continue to be, an exercise of modeling how WordPress works to a consistent (RESTful) interface. Furthermore, this model is declared in a common language for clients to interpret.
  • At some point in the future, WP-CLI will be able to ditch a substantial amount of its internals when it can use the WP REST API as its interface to WordPress. WP-CLI can continue to serve as the fastest way for developers to manage WordPress, offering higher-level meta operations like generate, (push|pull), and clone in addition to being a seamless command line interface to WordPress internals.
  • As WordPress developers write new endpoints for the WP REST API, it will be quite powerful to have those endpoints instantly accessible through the command line, and as accessible as core resources. For instance, where WP-CLI currently has the wp post * commands, WP-CLI requires Easy Digital Downloads to produce its own wp edd * commands.
  • It appears to be supremely possible to deliver this project as a series of improvements to WP-CLI, shipped over one or more versions in the next couple of quarters.

Lots of threads to pull on.

I’m starting development by working towards making wp tag list work interchangably with local and remote sites. Doing so already raises a few issues:

  • WP-CLI needs to be easier to register commands on the fly. In my prototype, I had to eval() a dynamically generated class. It would be much nicer to be able to register an arbitrary function, closure, or method as a WP-CLI command.
  • When we register REST endpoints to WP-CLI on the fly, there’s the potential for them to conflict with existing commands. Furthermore, the endpoints will vary from site to site. Ideally, the commands you see should represent the commands available on the target site. I think site aliases may offer us a backwards-compatible implementation; for instance, specifying an alias like wp @prod would only expose commands available on production.
  • Remote calls will need authentication. Ideally, it should be possible to authenticate once through a supported protocol (basic, oAuth1, API key, etc.), and store these authentication details somewhere on the file server. This is potential rationale for a config management command. If you aren’t blocking web requests to wp-cli.yml and wp-cli.local.yml already, you should be.

This project is made possible thanks to the backing of many generous organizations and individuals. Thank you again for supporting me on this journey.


Pressed offers white-label, fully managed, WordPress hosting, built on Amazon’s cloud infrastructure. Launch your own managed WordPress hosting brand and let us handle all the maintenance, updates, customer support and billing while building a new recurring revenue stream for your business.


Chris Lema is the CTO & Chief Strategist at Crowd Favorite. He’s also a WordPress evangelist, public speaker & advisor to product companies. Human Made is a leading WordPress Development, Hosting and Consultancy Firm with a global team covering Europe, The USA, and Asia/Australia.
Pagely® is the World’s first and most scalable WordPress Hosting platform: We help the biggest brands scale and secure WordPress. Pantheon is a website management platform used to build, launch, and run awesome Drupal & WordPress websites.



Aaron Jorbin, Aki Björklund, Anu Gupta, Bjørn Ensover Johansen, Brian Krogsgard, Bronson Quick, Chuck Reynolds, Corey McKrill, Daniel Hüsken, Dave McDonald, Dave Wardle, Eli Silverman, Felix Arntz, Howard Jacobson, Japh Thomson, Jason Resnick, Jeremy Felt, Justin Kopepasah, Kailey Lampert, Kevin Cristiano, Max Cutler, Mike Little, Mike Waggoner, Nate Wright, Pippin Williamson, Quasel, Ralf Hortt, Richard Aber, Richard Wiggins, Ryan Duff, Scott Kingsley Clark, Shinichi Nishikawa, Sven Hofmann, Takayuki Miyauchi, Tom McFarlin, rtCamp

Let’s go!

Version 0.22.0 released

by danielbachhuber, on 07 Jan 2016

Happy 2016! I thought you might enjoy a new WP-CLI release before I dive into the RESTful CLI project.

Use wp cli update to install v0.22.0, representing 137 resolved issues and pull requests. Here’s what’s new.

search-replace for love and profit

Last month, Pantheon generously sponsored 15 hours of my time to address some of the long-standing bugs in the backlog, and make a few substantial enhancements too.

Let’s start with the good stuff:

  • Performance boost! Instead of running a MYSQL LIKE statement every 1000 rows, WP-CLI now just runs it once [#2304]. On a post meta table of ~3.5 million rows where 75,610 rows were affected, this change improved execution time from 734.926s to 225.509s (3.3x faster).
  • Use the --export=<filename> argument to create a SQL file of your transformed data, instead of making updates to the database [#2254]. This is a helpful feature when you want to prepare a database for a new environment without having to import and then run search-replace.
  • Wildcards can be used in table names [#2233]. search-replace against just meta tables with wp search-replace <old-string> <new-string> '*meta*'. Note: the pattern needs to be quoted, as * is a special character in Bash.

I also landed a number of search-replace bug fixes and minor enhancements:

  • Recurses objects by default when replacing inside of serialized data [#2222]. Among other things, this ensures theme mods are transformed as expected. You can disable the behavior with --no-recurse-objects. But, if you do disable the behavior, I’d like to hear from you. I think this is an unnecessary option we could remove at a later date.
  • Properly escapes quotes used in search or replace strings [#2230].
  • Lets users know to flush their persistent object cache after a search-replace procedure is performed [#2236].
  • Bails early when the replacement string is the same as the search string [#2235].
  • Indicates execution time when running search/replace with --verbose [#2242].
  • Prevents unnecessary calls to $wpdb->update() when there are no replacements to be made [#2245].
  • Drops unnecessary REGEXP query when in regex mode [#2305].

Changes to supported versions

WP-CLI’s minimum supported WordPress version is now 3.7 [#2261].

We also officially support PHP 7 [#2330].

Everything else in v0.22.0

Improvements to wp scaffold (plugin|plugin-tests):

  • Makes Travis less noisy by only sending email notifications on the initial build failure for a branch [#2194].
  • Plugin header follows WordPress’ PHPDoc standards [#2197].
  • Adds .dist extension to PHPUnit config file to permit overriding with a local config file [#2247]
  • Parses readme.txt to find WordPress versions to use as Travis tested versions [#2255].
  • Includes a default .gitignore [#2297].

New flags for existing commands:

  • wp core update --minor to only perform minor updates [#2256].
  • wp (post|comment|user) meta delete <id> --all to delete all meta values on a given object [#2265].
  • wp core update-db --dry-run to see whether a database needs an upgrade [#2293].
  • wp media regenerate --only-missing for faster performance on sites with lots of images where only a small number are missing sizes [#2292].
  • wp cron event run --all to execute all registered cron events [#2323].
  • wp site empty --uploads to empty a site and delete its uploads directory too [#2339].
  • wp core install --skip-email to install without email notifications [#2345].
  • wp transient (get|set|delete) --network to manage site transients [#2351].

Framework enhancements:

  • Introduces wp_version_compare() when comparing WordPress versions [#2237]. SVN and Git tags include -src in $wp_version, which version_compare() doesn’t like.
  • Defers to the $PAGER environment variable when set [#2264].
  • Introduces a composer.lock file to the project, to fix dependencies to specific hashes [#2280].
  • Magically globalizes any new variables defined in wp-config.php, as they’re expected to be global [#2318].
  • If a --require=<file> is missing, specifies the context of where the missing file is referenced for easier debugging [#2336].
  • Use mustangostang/spyc instead of bundling our own copy [#2350]. The Spyc class is still available in the global namespace.
  • Introduces WP_CLI\Utils\get_temp_dir() for safer temp directories [#2353].

Improvements to other commands:

  • Includes not_found label when scaffolding a custom taxonomy [#2196].
  • Permits installing remote plugin files without .zip in filename [#2193].
  • Warns when adding a user as a super admin when the user is already a super admin [#2202].
  • Uses WP_CLI::log() instead of WP_CLI::line() in wp import, so --quiet flag is respected [#2234].
  • Adds support to wp db tables for wildcard tables (e.g. *meta*), --all-tables-with-prefix, and --format=csv [#2250].
  • Improves error message when installing a plugin or theme and the resource isn’t found [#2253,#2267].
  • Supports custom wp export filename formats with --filename_format=<format> [#2230]
  • Assumes db errors during wp install to be installation failures, and reports accordingly [#2337].
  • Exposes plugin header details at runtime for wp scaffold plugin [#2338].
  • Includes ci/behat-tags.php in wp scaffold package-tests [#2342].

Bug fixes across the board:

  • Lets help run early when WP is detected, but not installed (e.g. wp core config --help) [#2190]. Bug was introduced in v0.20.0.
  • When scaffolding a child theme, creates a safe version of the parent theme slug for the child enqueue function [#2203]. Previously, if the parent slug included dashes, an invalid enqueue function would be scaffolded.
  • Suppresses error notices when looking for wp-config.php and PHP’s open_basedir is in effect [#2211].
  • Fixes error notice in WP_CLI\Loggers\Quiet [#2210].
  • Fixes all_items label in custom post type scaffolding [#2213].
  • Ensures install-package-tests.sh actually downloads the nightly WP-CLI Phar build, and not a redirect [#2214].
  • Sets the upload_space_check_disabled to 1 when installing multisite [#2238]. This mirrors core’s behavior on new installs.
  • Provides a more helpful message when image regeneration fails [#2239].
  • Properly updates menu sub-item parent when parent is deleted [#2262].
  • Stops prefixing rewrite rules with index.php when using wp rewrite structure [#2279].
  • Fixes typo in wp transient set synopsis [#2282].
  • Restores wp core verify-checksums for non-US English locales [#2287]. Bug was introduced in v0.21.0.
  • Switches to the readline library, when available, for better support of arrow keys using --prompt [#2325].
  • WP_CLI\Formatter properly checks for null values on objects [#2322].
  • In wp media import, uses host instead of scheme to determine whether a file is remote or local, for Windows compatibility [#2324].
  • Ensures updating a plugin with an invalid --version=<version> specified doesn’t delete the plugin [#2346].

Contributors to this release: 2ndkauboy, coreyworrell, danielbachhuber, davidleach, duncanjbrown, ernilambar, fjarrett, gilbitron, greg-1-anderson, iandunn, jjeaton, modelm, rodrigoprimo, ryanshoover, stevector, szepeviktor, tristanpenman, x1024

You can browse the full list of resolved issues on GitHub.

What the survey said, 2015 edition

by danielbachhuber, on 01 Dec 2015

Many thanks to the 206 (!!!) people who took our second user survey. We appreciate your time in helping us to understand how WP-CLI is being adopted by the community.

Curious as to how the numbers have changed? Take a look at the summary of the first user survey from April 2014.

By the numbers

85% of respondents use WP-CLI regularly

Of this 85%, 48% use WP-CLI multiple times per day. 37% use it a couple or few times per week. Only 15% of respondents use WP-CLI infrequently or rarely.

94% of respondents use WP-CLI interactively at the command line, 66% have incorporated it into bash scripts, and 23% are using WP-CLI with Puppet, Chef, or another provisioning system. Other tools mentioned include: Capistrano, Codeception, EasyEngine, Fabric, Grunt, and SaltStack.

Most users keep WP-CLI up to date

Over 70% of respondents keep WP-CLI up to date. Here’s how the numbers break down:

  • 13% run the latest alpha. You can too with wp cli update --nightly.
  • 58% use the latest stable release (v0.20.x at time of survey).
  • 24% are using one or two versions below the latest stable. Only 5% use a very old version of WP-CLI.

Good news — if you’re writing custom commands, you can reasonably assume it’s safe to use the latest features in WP-CLI.

WP-CLI is used for an increasing variety of tasks

Like last year, the survey included “What do you use WP-CLI for?” as a free-form field. To produce a statistical summary, I tagged each response with keywords. Of 170 interpreted values:

  • 38% (65) use WP-CLI for updating WordPress core, themes, or plugins.
  • 22% (38) transform their database in some way using wp search-replace.
  • 17% (29) rely upon WP-CLI when performing migrations.
  • 15% (26) make use of WP-CLI’s database management features: wp db export, wp db import and wp db optimize.
  • 11% (18) depend upon WP-CLI in provisioning scripts.
  • 10% (17) scaffold new themes and plugins with wp scaffold.
  • 9% (16) write custom commands for their own needs.
  • 6% (10) generate mock posts, users and comments.
  • 3% (5) are hearty souls who use wp shell, wp eval, and wp eval-file for debugging and quick scripts.

In no particular order, here are some third-party commands and workflows mentioned: Jetpack CLI, WP Parser, ElasticPress, WP Migrate DB Pro, WP CFM, BackWPUp, wp-cli-ssh, wp-instant-setup, project-template-wordpress, and provisioning a new WordPress.org Theme Review environment.

One person said they use WP-CLI to make coffee. On behalf of everyone, I look forward to the day I can install this command from the package directory.

Feature requests

Feel like contributing to WP-CLI over the holidays? Here’s a grab bag of enhancements you could work on:

  • Better documentation (internals, extending, common workflows).
  • One single uber-command to install WordPress, including downloading files, creating the MySQL database, setting up wp-config.php, and populating database tables.
  • Suggest correct syntax when a command is incorrectly entered (e.g. git staus).
  • Improved support for managing multiple networks: wp network list, wp network create.
  • Install plugins favorited by a given WordPress.org user.
  • Verify theme and plugin checksums.
  • Report when extra files are present in wp-admin or wp-includes (e.g. checksums of directories)
  • Save a template of a WordPress setup (similar to grunt {my-task}).
  • Disable all plugins except for a specific one. Or, load WP-CLI with only a given plugin active.
  • Install WordPress nightly builds without needing the beta plugin.
  • Provide a command to execute WP-Cron without requiring a HTTP request.
  • Define custom scaffolds for themes and plugins.
  • Generate posts, pages from a sitemap CSV.
  • Magically migrate data between environments (production -> staging).
  • Add option to exclude specific tables in wp search-replace.
  • Provide a way to log in with a one-time link.

If you can’t find an existing GitHub issue, please create one and we can begin discussing implementation.

Thanks again to everyone who took the time to complete our user survey! May WP-CLI continue to be a shining light for your WordPress development needs.

Versions 0.21.1 and 0.20.4 released

by danielbachhuber, on 23 Nov 2015

WordPress 4.4 loads a few new files even more files in wp-settings.php. Because WP-CLI has a custom wp-settings-cli.php (background), WP-CLI v0.21.1 and v0.20.4 are compatibility releases to load these new files.

Importantly, due to the nature of these changes, WP-CLI versions prior to 0.20.4 will be incompatible with WordPress 4.4.

Inspect the nature of the change in this pull request.

Contributors to this release: danielbachhuber

Version 0.21.0 released

by danielbachhuber, on 04 Nov 2015

It’s been a pretty crazy week so far, and it’s only Wednesday.

As many of you are aware of, I launched a Kickstarter campaign Monday night: A more RESTful WP-CLI. Incredibly, it was 100% funded in just under 12 hours. Check out the link for more details on what I’ll be working on in early 2016.

But, I have an even more important note about supported WordPress versions:

  • WP-CLI v0.22.0 (the next release) will bump the minimum supported WordPress version from 3.5 to 3.7 (background).
  • WP-CLI versions prior to 0.20.3 will be incompatible with WordPress 4.4 (background).

This important note may apply to you in some way – please take action accordingly.

WP-CLI v0.21.0 represents 109 resolved issues and pull requests. Here’s what’s new.

Load WordPress with WP_CLI::get_runner()->load_wordpress();

For a while now, you’ve been able to run a WP-CLI command before WordPress loads by adding @when before_wp_load to your command docs. Now, you can load WordPress that same command.

See how wp eval makes use of WP_CLI::get_runner()->load_wordpress():

class Eval_Command extends WP_CLI_Command {

     * Execute arbitrary PHP code.
     * <php-code>
     * : The code to execute, as a string.
     * [--skip-wordpress]
     * : Execute code without loading WordPress.
     * @when before_wp_load
     * ## EXAMPLES
     *     wp eval 'echo WP_CONTENT_DIR;'
    public function __invoke( $args, $assoc_args ) {
        if ( null === Utils\get_flag_value( $assoc_args, 'skip-wordpress' ) ) {
        eval( $args[0] );

Use wp eval --skip-wordpress <code> to execute PHP without loading WordPress. Or, use wp eval-file --skip-wordpress <file> to, say, execute a quick and dirty script leveraging WP-CLI utilities without needing a WordPress install present.

Similarly, when you specify a --version=<version> with wp core verify-checksums, WP-CLI will check to ensure core files haven’t been modified – without loading WordPress.

Creating this new feature was almost like replacing the engine on a moving car.

More verbosity with --debug

When you supply the --debug flag, you’ll get more context into how WP-CLI is executing your command.


$ wp option get home --debug


$ wp option get home --debug
Debug: Using default global config: /home/vagrant/.wp-cli/config.yml (0.026s)
Debug: No project config found (0.027s)
Debug: Required file from config: /srv/www/wp-rest-cli/wp-rest-cli.php (0.059s)
Debug: ABSPATH defined: /srv/www/wordpress-test.dev/ (0.06s)
Debug: Begin WordPress load (0.063s)
Debug: wp-config.php path: /srv/www/wordpress-test.dev/wp-config.php (0.065s)
Debug: Set URL: wordpress-test.dev/ (0.066s)
Debug: Loaded WordPress (0.515s)
Debug: Running command: option get (0.516s)

Make your own commands more helpful by including WP_CLI::debug( $debug_message ); at key checkpoints.

Other changes in v0.21.0


  • Use wp core update-db --network to upgrade databases across an entire network; also improves verbosity for this command by providing the from and to database versions.
  • Adds a wp comment recount command for recalculating a post’s comment count.
  • Includes wp taxonomy list, wp taxonomy get, wp post-type list and wp post-type get for getting details about registered taxonomies and post types.
  • Use --defer-term-counting with wp post update or wp post delete to recalculate term count at the end of the operation for increased performance.
  • Returns a more useful error message when running wp scaffold plugin-tests with an invalid plugin slug.
  • In wp theme list, indicates a parent theme by setting the status=parent. This more clearly distinguishes between a genuinely inactive theme vs. an “inactive” theme being used as a parent theme.
  • Displays error message when trying to use wp rewrite flush --hard on a multisite install, because WordPress doesn’t generate a .htaccess file for multisite installs.
  • Permits --path for wp core download, even when WP is detected. This lets a custom WP-CLI command download WP to a subdirectory, when loaded from the scope of an existing WP install.
  • Adds --post_type__not_in argument to wp export, which can now produce an export without, say, feedbacks.
  • Permits space-delimited IDs for wp export --post__in. This makes it easier to pass IDs returned by wp post list --format=ids.
  • Includes an .editorconfig when using wp scaffold plugin.
  • Newly-generated install-wp-tests.sh files support $WP_VERSION=trunk or $WP_VERSION=nightly for running your plugin tests against WordPress trunk.
  • Warns user when wp-config.php isn’t writable for wp core multisite-convert.
  • Provides a more helpful error message when an invalid subcommand has a valid parent command.
  • Supports using WP-CLI on a WordPress instance backed by APC cache, but only after warning and requiring confirmation.
  • Sniffs out the custom vendor path if WP-CLI is a part of a larger Composer project.
  • Deprecates wp post url in favor of wp post list --field=url, but includes a backwards compatability shim for wp post url.
  • Supports --autoload=(yes|no) when using wp option update (requires WordPress 4.2).
  • Supports multiple comment ids for wp comment (spam|trash|approve).
  • Paginates help output on Windows using more.
  • Provides human-friendly output when rewrite rules successfully flush.

Bug fixes:

  • Generic arguments are no longer required for wp post create and wp comment create, allowing use of those commands without supplying, say, a title.
  • Accepts associative args with no right-side data.
  • Exhausts all possible options when looking for PHP binary with wp cli update. Previously, WP-CLI would try to call php directly, which would fail on systems where PHP wasn’t exposed as such.
  • Accommodates wp_new_user_notification()’s deprecated second argument, depending on WordPress version.

You can browse the full list of resolved issues on Github.

Contributors to this release: aaemnnosttv, borekb, danielbachhuber, gmcinnes, JRGould, johnbillion, kraftbj, miya0001, ntwb, rodrigoprimo, rmccue szepeviktor, torounit, voldemortensen, ypid

Version 0.20.3 released

by danielbachhuber, on 30 Oct 2015

WordPress 4.4 loads a few new files in wp-settings.php relating to oEmbed and the REST API. Because WP-CLI has a custom wp-settings-cli.php (background), WP-CLI v0.20.3 is a compatibility release to load these new files.

Importantly, due to the nature of these changes, WP-CLI versions prior to 0.20.3 will be incompatible with WordPress 4.4.

Stay tuned next week for WP-CLI v0.21.0 (which is also compatible with WordPress 4.4), the results of the user survey, and a special announcement.

You can browse the full list of resolved issues on GitHub.

Contributors to this release: danielbachhuber, kraftbj, rmccue