On launching WP Remote Premium

This past Friday, after months of work and then two weeks of hard work, we launched a Premium version of WP Remote. The new feature list includes:

  • Automatic backups to WP Remote, or Dropbox or your own S3 account.
  • Daily email summaries of what’s been happening on your sites.
  • Ability to install, activate, deactivate, and delete themes and plugins.
  • Automatic core, theme, and plugin updates.
  • History now logs important actions happening within WordPress, including when a theme is switched, an administrator logs in, etc.

What I’m most excited about, and what WP Tavern nailed in their coverage, is that the launch of Premium also marks the formal announcement of the WP Remote API. Our entire JavaScript web application is built upon the same API that’s available to the public. For our users, this means the sky is the limit in how they integrate WP Remote into their workflow. For Human Made, the API is a rock solid foundation for us to continue to build WP Remote experiences with. It also means we can develop the API independently of the user-facing application.

The API

Think of the WP Remote API as a single hub for managing all of your remote sites. Our eventual goal is to offer an endpoint for manipulating each object inside of WordPress. Right now, we offer Plugin, Theme, User, and Option.

In the process of launching Premium, we did a complete rewrite of the API codebase. Our rewrite offered a two-fold advantage: adding a new endpoint is vastly simplified, and our API docs are automatically generated through Reflection.

As an example, read through the site/option endpoint:

<?php
namespace WPRemoteAPI;
/**
 * Manage a Site's options
 */
class Site_Option_Endpoint extends Site_Endpoint {

	protected $pattern = 'site/{SITE_ID}/option/{OPTION_NAME}';
	protected $query = 'site_id=$matches[1]&option_name=$matches[2]';
	protected $arguments = array(
		'option_value' => array(
			'required'          => true,
			'sanitize_callback' => false,
			'methods'           => array( 'POST' )
			),
		);
	protected $methods = array( 'GET', 'POST', 'DELETE' );
	protected $authenticated = true;
	protected $premium = true;

	protected function get() {

		$result = $this->site->options->get_option( $this->option_name );

		if ( is_wp_error( $result ) )
			$this->send_error( $result->get_error_message(), 500 );

		$this->send_response( $result );

	}

	protected function post( $args ) {

		$result = $this->site->options->update_option( $this->option_name, $args['option_value'] );

		if ( is_wp_error( $result ) )
			$this->send_error( $result->get_error_message(), 500 );

		$log_args = array(
			'type'         => 'option',
			'action'       => 'update',
			'option_name'  => $this->option_name,
			'option_value' => $args['option_value']
			);
		$this->site->log->add_item( $log_args );

	}

	protected function delete() {

		$result = $this->site->options->delete_option( $this->option_name );

		if ( is_wp_error( $result ) )
			$this->send_error( $result->get_error_message(), 500 );

		$log_args = array(
			'type'         => 'option',
			'action'       => 'delete',
			'option_name'  => $this->option_name
			);
		$this->site->log->add_item( $log_args );

	}

	protected function validate_query_vars( $query_vars ) {

		$query_vars = parent::validate_query_vars( $query_vars );

		if ( empty( $query_vars['option_name'] ) )
			$this->send_error( 'Invalid option.', 404 );

		$this->option_name = sanitize_text_field( $query_vars['option_name'] );
	}

}

WPRemote_API()->add_endpoint( new Site_Option_Endpoint );

At the top of the class, we define:

  • Endpoint pattern. This is then translated into regex, and registered with WordPress’ Rewrite API using HM Rewrite.
  • Query args, or how the pattern should be translated into usable data. Each endpoint class has access to a validate_query_vars() method which validates and sanitizes the passed arguments. For this endpoint, you can see how it first calls its parent’s validator, and then validates on its own.
  • Supported arguments. Additional arguments can be defined method by method. Each argument can have a few attributes: whether or not it’s required, sanitization callback, which methods use the argument, and, not shown here, a default value. The arguments are automagically processed by the base endpoint class, and passed into the method’s callback.
  • Supported methods, and whether or not the endpoint requires authentication or a Premium account. Fairly self-explanatory, I hope.

These class attributes communicate the endpoint’s qualities in a structured way. In addition to providing us a highly-usable abstraction of code, they also form the basis of our documentation. Through PHPdoc and Reflection, we can provide human-readable documentation at the endpoint-level and the method-level.

Notice we’ve yet to implement argument descriptions and mock endpoint responses. Human Made is hiring a full-time PHP product developer — come help us sort it out!

Launch Process

WP Remote Premium is the second significant WP Remote launch process I’ve participated in. The first was Automatic Backups in June. At the end of the first, we did a post-mortem to identify what went well, and what we’d like to improve upon. Fortunately, we don’t seem to be repeating any past mistakes.

With this launch, here are a few pieces I think we did well:

Met our launch date. The Friday before WordCamp Europe had been set for a couple of months. A few days before, we set an announcement time of 9 am GMT. We formally announced the product at 9:45 am GMT on Friday. Booyah, #shipping.

Focused on what needed to be done, vs. what we wanted to do. Every product launch is death by a thousand decisions of bugs to fix, features to add, and tweaks to make. Making decisions from the perspective of what needs to be done forced a clarity to our efforts. In the last 24 hours before launch, we worked against a “Premium Launch Blockers” milestone. When that cleared, we launched.

Brought everyone together for the final push. As much as I love working distributed, there’s nothing like being in physical proximity to build excitement for shipping. Collaboration is higher bandwidth, and it’s much easier to triage, reallocate, etc.

Next time we launch a substantial addition to a product, here are a couple pieces I’d like us to do better:

Longer beta test period. Premium was open for beta testers three days before launch. The functionality for Premium was largely done, however the UI wasn’t in place. There haven’t been any huge bugs uncovered, but beta periods are always nice for fit and finish. Ideally, any large release should be in beta for a couple of weeks.

Spend more time on marketing. Although it wasn’t as rushed as Automatic Backups, I’d mark our marketing strategy as “bare minimum plus one.” We didn’t send any materials to publications until a day prior, largely because the UI was still in flux, and owe thanks to Post Status and WP Tavern for the quick turnaround. On our end, we banged through documentation, an announcement post, and some tweets. In the future, we should be working on marketing for a couple or few weeks.

Ultimately, the launch of WP Remote Premium met or exceeded many of my goals. Renewed focus on offering all of our functionality through a public API is what sets us apart from the alternatives, and will make WP Remote a durable product for the long-term. I’m excited to see what people come up with.

Author: Daniel Bachhuber

Proud father x2. Principal, Hand Built. Maintainer, WP-CLI.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s