RESTful WP-CLI: The final update?

This post originally appeared on the WP-CLI blog.

Last November, I published a Kickstarter, and was completely blown away by the support. This month, the funding ran out, so I thought I’d post one last RESTful WP-CLI update.

Actually, the story doesn’t end here. I’m writing a massive retrospective post about using Kickstarter to fund open source, so keep an eye out for that. Also, WP-CLI v0.24.0 is due out a week from now, July 27th, and it’s looking to be the largest release ever. When you do a Kickstarter, it’s really just the beginning of something bigger.

Enough with the superlatives, let’s dive into some new features. Remember: RESTful WP-CLI features require 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 wp-cli/restful to install the latest.

Effortlessly use WP-CLI against any WordPress install

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, if I’m working locally on the runcommand theme, have registered a new rewrite rule, and need to flush rewrites inside my Vagrant-based virtual machine, I can run:

$ wp @dev rewrite flush
Success: Rewrite rules flushed.

Then, once the code goes to production, I can run:

$ wp @prod rewrite flush
Success: Rewrite rules flushed.

Look ma! No more SSH’ing into machines, changing directories, and generally spending a full minute to get to a given WordPress install.

Additionally, alias groups let you register groups of aliases. If I want to run a command against both runcommand WordPress instances, I can use @both:

$ wp @both core check-update
Success: WordPress is at the latest version.
Success: WordPress is at the latest version.

Aliases can be registered in your project’s wp-cli.yml file, or your user’s global ~/.wp-cli/config.yml file:

@prod:
  ssh: [email protected]~/webapps/production
@dev:
  ssh: [email protected]/srv/www/runcommand.dev
@both:
  - @prod
  - @dev

But wait, what’s the ‘ssh’ in there?

WP-CLI now natively supports a --ssh=<host> global parameter for running a command against a remote WordPress install. Many thanks to XWP and their community for paving the way with WP-CLI SSH.

Under the hood, WP-CLI proxies commands to the ssh executable, which then passes them to WP-CLI installed on the remote machine. Your syntax for -ssh=<host> can be any of the following:

  • Just the host (e.g. wp --ssh=runcommand.io), which means the user will be inferred from your current system user, and the path will be the SSH user’s home directory.
  • The user and the host (e.g. wp [email protected]).
  • The user, the host, and the path to the WordPress install (e.g. wp [email protected]~/webapps/production). The path comes immediately after the TLD of the host.

Or, if you use a ~/.ssh/config, <host> can be any host alias stored in the SSH config (e.g. wp --ssh=rc for me).

Note you do need a copy of WP-CLI on the remote server, accessible as wp. Futhermore, --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 of how you can make wp accessible.

RESTful WP-CLI v0.2.0 and beyond

Today marks the release of RESTful WP-CLI v0.2.0. Among 43 closed issues and pull requests, I’d like to highlight two new features.

First, use wp rest (post|user|comment|*) generate to create an arbitrary number of any resource:

$ wp @wpdev rest post generate --count=50 --title="Test Post"
Generating items  100% [==============================================] 0:01 / 0:02

When working on a site locally, you often need dummy content to work with. There are a myriad of ways custom post types can store data in the database though, so generating dummy content can be a painstaking process. Because the WP REST API represents a layer of abstraction between the client (e.g. WP-CLI in this case) and the database, it’s much easier to produce a general purpose content generation command.

In the future, I’d love to see dummy data generated for each field based on the resource schema.

Second, use wp rest (post|user|comment|*) diff to compare resources between two enviroments:

# "command" isn't a typo in this example; "command" is a content type expressed through the WP REST API on runcommand.io
$ wp @dev rest command diff @prod find-unused-themes --fields=title
(-) http://runcommand.dev/api/ (+) https://runcommand.io/api/
  command:
  + title: find-unused-themes

When working with multiple WordPress environments, you may want to know how these environments differ. Because the WP REST API represents a higher-level abstraction on top of WordPress, computing the difference between two environments becomes a matter of fetching the data and producing a comparison.

There are a number of ways the diff command could be improved, so consider this implementation to be the prototype.

What’s next?

More immediately, I’d like to start looking at how well RESTful WP-CLI works with plugins and themes. If you’ve written custom endpoints for the WP REST API, please weigh in on this Github issue so I can check it out.

Ultimately, the goal is for wp rest post to replace wp post, but there are many months between here and there. In this future where WP-CLI packages are first-class citizens amongst the commands in WP-CLI core, RESTful WP-CLI gets to serve as a testbed for figuring out how that actually works. We shall see, we shall see.

As always, thanks for your support!

RESTful WP-CLI: What I’ve been hacking on

This post originally appeared on the WP-CLI blog.

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:
1:
  - 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
2:
  - 0.000257 seconds
  - WP_REST_Posts_Controller->get_items, WP_Query->query, WP_Query->get_posts, WP_Query->set_found_posts
  - SELECT FOUND_ROWS()
3:
  - 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'
4:
  - 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
5:
  - 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)
6:
  - 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
7:
  - 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
 (1.598s)
+----+-----------------------------+
| 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
password:
slug:
status: draft
title:
  raw: Test post
  rendered: Test post
content:
  raw: Foo bar
  rendered: |
    |
        <p>Foo bar</p>
excerpt:
  raw:
  rendered: |
    |
        <p>Foo bar</p>
author: 1
featured_media: 0
comment_status: open
ping_status: open
sticky: false
format: standard
categories:
  - 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:[email protected] 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:[email protected] 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.

RESTful WP-CLI: No rest for the weary

This post originally appeared on the WP-CLI blog.

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 withwp cli update --nightly. Once you’ve done so, you can install wp-rest-cli withwp 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(
        array(
            'name'        => 'hook',
            'type'        => 'positional',
            'description' => 'The key for the action or filter.',
        ),
        array(
            '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 runwp 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 preceeding, @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.

#feelingrestful: A more RESTful WP-CLI

Earlier today, I gave a talk at A Day of REST about unlocking the potential of the WP REST API at the command line — by creating a more RESTful WP-CLI. Check out the project on Github, and stay tuned for the v0.1.0 release. Read on for my (loosely edited) annotated slides from the presentation.

Continue reading “#feelingrestful: A more RESTful WP-CLI”

RESTful WP-CLI – The journey begins

This post originally appeared on the WP-CLI blog.

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.