Remote Control WordPress at Scale

Avatar of Scott Fennell
Scott Fennell on (Updated on )

This is the third and final article in a series on “remote control WordPress”. That’s my nickname for this strategy of managing network settings on one “control” install, and then pulling those values into all your client installs. The advantage is that it saves staff members from having to toggle the same settings on the same network plugins, across many multisite installs.

Article Series

Part 1: The WP REST API for Remote Control WordPress
Part 2: OAuth Fun with OAuth1
Part 3: Remote Control WordPress at Scale (You are here!)

In the first article I demonstrated how to expose network settings for querying via the WP API. In the second article, I demonstrated how to query those settings, even when they require an OAuth header (which they always should). In this article, I’m going to put all of the pieces together and show you exactly how I’m using this in production.

What You’ll Need to Follow Along

If you’re familiar with the series, you know that we need two WordPress multisite installs to pull this off:

  • First, we need the control, where we’ll manage settings. I’m using my personal website as the control in these demos.
  • Second, we’ll need a client install to query settings from the control. I’m using my local MAMP for this.

Just like in the last article, the control install should have the WP REST API V2 plugin and also my CSS-Tricks WP API Control plugin network activated. These are dissected in the earlier articles, although it’s worth re-pulling the control plugin, as I recently fixed a PHP warning.

Sort of new territory: both the control install and the client install should have my CSS-Tricks WP API Client plugin network activated and fill-in-the-blanks’d. That process is described for the client install in the second article. Just go ahead and copy your filled-in version of that plugin into the control now as well; there’s no need to re-do the OAuth drudgery for the control.

Also sort of new territory: Now that you’ve performed the steps in the second article, you can go ahead and uninstall the OAuth1 plugin from the control.

Totally new territory: Both the control and the client need a feature plugin to demonstrate how to use the CSS-Tricks WP API Client in a meaningful way. I have prepared such a feature plugin for you, it’s called CSS-Tricks Can He Loginz?.

Got everything? Good. We’re about to get… abstract!

The Abstraction Layer

Things in this article series have gotten a little complex. In the second article, we did a ton of work in order to query network settings from the control blog. We did so via a demo shortcode that simply logged the results of the API call. In real life, I run many “feature” plugins to do things like Google Analytics, admin notices, white-labeling, you name it. If we want to manage the settings for all of those feature plugins via remote control, it would be really troubling if we copy/pasted all of that API work into each one. Therefore, we need one plugin that offers us a framework for getting network options from the control install, and all of our feature plugins can use that framework. We need an abstraction layer!

Wikipedia tells us,

In software engineering and computer science, abstraction is a technique for managing complexity of computer systems.

That’s exactly what I’m doing in my CSS-Tricks WP API Client plugin. Just browse the list of files and you’ll get a sense of where I’m going with this:

  1. `control_panel.php` renders a network settings UI in wp-admin/network/, which our feature plugin will instantiate. It has a handful of nuances that are crucial to this article, which I’ll detail below.
  2. `remote.php` calls the control install to query for a given network setting. It merges the remote settings with the local settings in order to actually hand off the full roster of settings to power a feature plugin.
  3. `crud.php` gets and sets network options. If the lack of a core API for network admin bothers you, crack open this file and consider getting excited about it. It’s playing only a minor role in this article.
  4. `oauth.php` authenticates to the control install. It’s used here, but not discussed. We dissected it last article.
  5. `demo.php` offers a hello-worlding shortcode for this entire process, which we did in the previous article. We’re neither discussing nor using it here.

Alone, this plugin does absolutely nothing, which is perfect. Feature plugins will make use of it in order to create, review, update, or delete network settings, including settings that are managed on the control install.

To be perfectly clear, the CSS-Tricks WP API Client plugin would still be pretty cool even if we weren’t doing all of this remote control stuff: Part of it’s value is that it gives us a consistent framework for doing network settings pages across our codebase, which is great in its own right.

I’ll deep-dive into the relevant portions of the CSS-Tricks WP API Client plugin later. For now, I want to introduce our feature plugin which will use the abstraction layer.

This Is It

Photograph of American singer-songwriter Kenny Loggins

Our feature plugin, CSS-Tricks Can He Loginz does one thing, and it does it well: It adds a photograph of American singer-songwriter Kenny Loggins to the wp-login page. The Can He Loginz plugin requires that the API Client plugin be active as well:

The feature plugin is active, but the abstraction plugin is not.

It carries two network options, one of which is managed on the control blog, the other is managed on each client install, just to demonstrate both scenarios. That’s an important point: A given plugin might have many settings, some of which are the same on all of your client installs, some of which differ. Our feature plugin has one of each.

The settings page for the feature plugin.

The example obviously is farcical (if you don’t know who Kenny Loggins is, don’t worry. Just know that his music is pretty ironically good at this point, and that the plugin settings relate to some of his song lyrics) but the ease with which this feature plugin integrates with the CSS-Tricks WP API Client is really cool. Check out this code, which registers our feature plugin with the abstraction layer. Done. The Can He Loginz plugin now has a settings page in network admin, with remote control functionality.

Let me emphasize that: Given the setup tasks we’ve accepted and performed thus far in the series, achieving remote control WordPress for a feature plugin is basically a one-liner.

The settings, silly as they may be, are defined in an array here. That’s where we could easily add, remove, or redefine settings.

Now, to see what the Can He Loginz plugin actually does with those settings, open a fresh browser and view the login page:

Login page as altered by our feature plugin.

As promised, it’s displaying our photo and using our options in a couple of text blocks.



The crux of the matter occurs in my function here called merge(), from my class that calls the control install. First, it grabs your client settings – the ones that you’d want to be able to customize on each blog. In our example, that’s the “shoes” checkbox. Next, it loops through all of the remote settings on the control install, such as the “danger zone” setting from our example, and adds them to the output as well. The result is sort of like if you’d called get_site_option() on your client and on the control, and merged the results – in fact that’s exactly what happens behind the scenes via the WP API.

To help illustrate this, notice a subtle difference between how the settings UI is displayed on the control vs the client:

Client on the left, control on the right.

In both screens, the first setting is a remote control setting, so when viewed from a client install, it’s disabled. That same setting, on the control, is enabled. Conversely, since the second setting happens to expect a differing value on each client install, it’s enabled on the client and disabled on the control. That logic occurs here, in my class that draws the control panel UI.

Also of note is caching. The client caches the result from the control, and it has the ability to break that cache by re-saving the settings from the client UI.

What is the Eggs:Basket Ratio Here?

In the comments section of the first article in the series, MF Simchock expressed concern that, if the control install experiences downtime, then all of the client installs will be unable to call their settings. While that is true, there are a few things we can do to mitigate this. A couple of those things are outside the scope of this series:

  • Do whatever you have to do to prevent web traffic from going to the control install. It serves one purpose: To set and serve network options to the client installs. Configure that server as such. I’m out of my element here, but I wonder if there would be a convenient way to whitelist by IP, where you’d maintain a list of your client install server IP’s, and check that all requests originate from that roster.
  • If your control install is on the same box as your client installs, it’s likely that an outage on the control would also be an outage on your client installs. Something like CloudFlare’s Always Online could help with that scenario.

What we can do from within our codebase is to never cache results that seem wonky. Check out this code, which reviews the HTTP response from the control, and neglects to cache the result if it’s 40x or 50x.

What About WP-CLI?

In the first article, commenter “cidas” suggested that we could more easily achieve remote control WordPress via WP-CLI. He’s not wrong. We could absolutely write a CLI script to either push or pull settings between the clients and the control. That’s not something I’m qualified to write about, and I have no plans to pursue it in production. A few reasons:

  1. It happens to be further outside my skillset than the API/plugin arena.
  2. If we require command-line skill as a hiring/training pre-req for our settings wranglers, it’s going to have a big impact on our business model. It could be a good impact, it could be a bad impact. Right now, I’m okay with no impact!
  3. I think a CLI solution would lend itself more to the control pushing data to client installs, rather than clients pulling from the control. That scares me because a buggy script could corrupt live client data.

Next Steps

The best thing that could possibly happen to this effort would be for the WP API to be fully merged into core, along with the OAuth1 plugin. Monitor those projects for updates if you embark on the remote control lifestyle.

Other than that, I’m not inclined to make this any fancier or complicated than it needs to be: I think it’s ready. My next move is to continue implementing it on larger, more heavily trafficked, more important projects, and to continue to smooth out any rough edges.

Certainly, you could unit test the heck out of it, which I’m not digging into in this article. You could also wire in support for more field types (color picker, datetime picker, whatever). And I guess you could make it prettier. I have exactly zero CSS for the settings page, which I actually consider a good thing.

I had sort of expected for this third article to be the longest and most complicated in the series, but there really isn’t much to it. After all, our feature plugin is able to join this remote control lifestyle with just a small handful of code, all of which is a low-complexity affair of declaring a slug and settings. It feels like an extremely weird and overly complex way to get a network setting, but I think it’s far better than handling the same setting many times across many installs. I’m ready to go full-speed ahead with this paradigm, and I want to make it even better. If you try working through it, I’d love to hear what the pain points are.

Article Series

Part 1: The WP REST API for Remote Control WordPress
Part 2: OAuth Fun with OAuth1
Part 3: Remote Control WordPress at Scale (You are here!)