When it comes to starting a new blog, one of the first search results that comes up is from WordPress. It can’t be denied that the team behind it has done an amazing job with the platform. You can easily create a new blog within minutes, and after a few tweaks here and there and if you’re not too picky, you can get even get content going live in under an hour. That’s no small feat.

That being said, there are also quite a number of complaints about the WordPress site UI, to the point that in some cases users are turned off and jump to other boats because they find the UI un-usable.

So in this article I want to show you the key concepts needed to keep the content you already have in Wordpress and skip their own UI and presentation layer. We will query their REST API in order to achieve what many like to call “Headless Wordpress”.

What does "Headless" Wordpress mean?

By definition, a headless system is one that has no UI, so applying it to our context (WordPress), it would be only reasonable for you to assume we’re talking about a UI-less WordPress. And although it would make sense for you to think like that, you would be wrong.

A headless WordPress does actually have a content management UI where you, as a content author, can create it, format it and even schedule it, but the publishing interface (i.e where the content is actually published into), that one is optional. And I say optional because as you know, WordPress does provide that UI, but you can avoid it by using their API.

The way a headless WordPress publishes content, is by having a REST API which the clients (really, all external applications who can speak REST) can use to pull content from.

Using WordPress this way is the organic result of the evolution of our web development industry and the way we’ve moved into not only the cloud, but an increasing number of devices and their own view of the Internet. Just because both your computer and your cell phone can access the Internet, it doesn’t mean they should (or will) present that content in the same way so the delivery method for multi-client, multi-channel APIs need to provide data in a consistent format throughout all client formats and channels.

Let’s now go deeper into the benefits of headless CMS and why you might want to go this way.

Why Headless Wordpress?

Why would you want to use WordPress’ API instead of publishing directly on their platform? What are the advantages of such a move, because honestly, having a platform where you can distribute your content without the need to understand how to code and style your pages sounds great. What could be even better that would merit the need to pick up those extra skills or even hire someone who already has them?

I’m glad you asked!

These are the main benefits of our headless friends:

Multi-channel access

The ability to deliver content in a generic, standard and unformatted representation (mainly JSON format) allows for whoever is receiving that content to pick and choose how they want to present it. This provides a great deal of flexibility, both, for the content author and the publishing party. The authors can reach an unlimited number of devices without targeting them while the latter can display all the content in the way they want.

Centralized content management for multi-channel distribution

Essentially, you as an author can create the content once and access a multitude of channels as long as they are interested in displaying it (in other words, whoever wants to consume the API).

Full control over the visualization of the content

This is a real benefit for the content publishers, and not directly for the authors. The publishers will have full control over how they show your content, if you happen to be the one who’s also consuming the API, then you can present it however you want, otherwise, you’re also losing control over the presentation of you media.

Do what you want with your content

It’s your party. You consume the API, and what you do with it is completely open. Usually WordPress means blogs, but you don’t have to stay within those limits. Do you want to create a SPA and display your content there? Are you building a desktop application and want to handle the knowledge database using WP? You got it! It’s full control at the tip of your fingers and the limit of what you can do with the data is completely up to you.

Rewrite as much of WordPress as you want

Maybe this one is not for everyone, but then again, maybe you’re one of those people who really hate how WordPress’ admin UI works but at the same time, are stuck using it. With the API you can re-write as much of the admin UI as you like/need in order to improve your administrative experience. Not everything is about creating and publishing your content, it can also be about running the site.

You can probably see a common theme with some of these points: freedom.

By using WordPress’ headless version (A.K.A its API), you gain freedom over what you do with the content and how you do it. This is not only true for this particular scenario, of course, any Headless CMS will provide you with these advantages.

In fact, some of them, such as ButterCMS are designed exclusively to be headless content management systems, so you don’t even get the optional publishing interface (mainly because these systems make it unnecessary), but they are considered some of the most flexible version of what we’re covering here in this article, so I encourage you to check them out if you haven’t yet.

How to use Wordpress as a Headless CMS: An Example

Now that I’ve covered why you would switch from a classic WordPress instance (or as you’re about to read, any traditional cms) into a headless version, let me show you an example of what that would look like.

But first, in order to start using WordPress’ API and the basic auth security scheme (which is what I’ll be using here for the sake of time and brevity), you’ll need to install a couple of plugins:

WordPress’ REST API plugin (version 2).

WP-API Basic Auth handler.

They’re both really easy to install and require no configuration, so it’s just a matter of clicking a few links and reading a couple of bullet points.

For this article, I’m going to be assuming you’ve already installed a local copy of WordPress and are using that to test. If instead, you’re using an online version, you’ll have to have a Business level account in order to be able to install a plugin. The process after that is exactly the same, no matter what kind of installation you’re working with.

Now, back to the point. In order to show you what an API client would look like, I’ll be using Node.js to create a simple CLI tool that in turn, will have enough capabilities to list all published posts, Create a new basic post in Draft mode, Retrieve the content of a single published post, Publish a draft post and finally Delete a single post at a time. I’m choosing to do a command line tool in order to show you how versatile the API approach can be.

You could take my JS code and turn it into your own React or Vue SPA, just as easy.

In order to demonstrate what I’m going to be doing, here is a sample sequence of commands and outputs from our CLI tool:

~/wp-client:$ ./bin/run post:list * ( 7 ) Test post [ 2019-03-12T05:31:20 ] * ( 1 ) Hello world! [ 2019-03-12T05:18:51 ]

~/wp-client:$ ./bin/run post:create --title="Second tests post" --body="This is the body of the blog post" --username=deleteman --password=xxxx * NEW POST CREATED WITH ID: 10 * Title: Second tests post

~/wp-client:$ ./bin/run post:list * ( 7 ) Test post [ 2019-03-12T05:31:20 ] * ( 1 ) Hello world! [ 2019-03-12T05:18:51 ]

~/wp-client:$ ./bin/run post:publish --postId=10 --username=deleteman --password=xxxx Your post was published!

~/wp-client:$ ./bin/run post:list * ( 10 ) Second tests post [ 2019-03-12T05:33:39 ] * ( 7 ) Test post [ 2019-03-12T05:31:20 ] * ( 1 ) Hello world! [ 2019-03-12T05:18:51 ]

~/wp-client:$ ./bin/run post:get --postId=10 Second tests post ========= <p>This is the body of the blog post</p>

~/wp-client:$ ./bin/run post:delete --postId=10 --username=deleteman --password=xxxx Your post was deleted!

~/wp-client:$ ./bin/run post:list * ( 7 ) Test post [ 2019-03-12T05:31:20 ] * ( 1 ) Hello world! [ 2019-03-12T05:18:51 ]

Essentially what these commands try to show is:

The list of all published posts The creation of a new draft - notice how for this action we need to provide credentials Another list to show how the new draft is not yet showing up on our lists The publish action which is simply an update on the status of the post Getting the actual content of the post Deleting it - again we need to provide credentials here And finally, the last list to show how the deleted post is no longer available.

The full version of this code, will be available in GitHub in case you want to look at it, but let’s go into a bit more details shall we?

The API itself

WordsPress’ API is not that complex, and they have a very detailed handbook online to help developers learn how to use it.

For the purpose of this particular article, I only used one type of resource: posts

Which means my list of endpoints is limited to the following:

Verb Endpoint Description GET /wp/v2/posts Returns a list of posts (only the published ones) on the site GET /wp/v2/posts/:id Returns the content of one particular post. It must also be published, otherwise nothing will be returned. POST /wp/v2/posts/:id Partially updates a single post. This one is a bit weird, I have to admit. I would’ve gone with a PATCH or PUT verb instead of POST, but then again, there is nothing saying they can’t do what they did. POST /wp/v2/posts Creates a brand new draft post. DELETE /wp/v2/posts/:id Deletes a single post by ID

As you can see, the list is pretty simple and short. Thankfully the design of the API is quite RESTful, all the way to the point where they use HATEOAS to provide you with related actions on every response as well as their choice of actions for the HTTP methods (something that’s not always the case).

Since I’m using the config module to keep track of my configuration files, you can see how I added all this data into a single JSON (which stands for Javascript Object Notation) inside the config folder:

{ "wp-api": { "url": "http://www.example.com/wp-json", "endpoints": { "list": "/wp/v2/posts", "get": "/wp/v2/posts/:id", "update": "/wp/v2/posts/:id", "delete": "/wp/v2/posts/:id", "create": "/wp/v2/posts" } } }

I can then access this data with a simple config.get(‘wp-api.url’) for example, but more on this in a bit.

Creating the CLI

To help me on this task of creating the CLI, I used a great little module called OClif, which takes care of all the details regarding command line interfaces and how to deal with arguments. I’ve written a couple of articles about it, in case you’re not familiar with it, I would suggest checking them out.

The main project structure though, will be generated by this module, we’ll only be adding files into it. Particularly, the files required for every sub-command shown in the example above.

For this CLI, we’ll get with a basic structure where we have a single file per command (meaning, one file for post:create, one for post:publish and so on), one view file per command as well, and a single API client module, which will centralize the code required to communicate with our API.

In other words, the structure of our project will look like this:

Command files

All the command files will be similar in the sense that they will define instances of the Command class, and they will implement the run method where we’ll perform some basic validation (especially for the ones that require several flags). And then we’ll just interact with the client module.

Here is an example of one of the commands- the create command:

'use strict' const {Command, flags} = require('@oclif/command') const config = require("config") const wpClient = require("../../../lib/wpclient") const V = require("../../views/createView") class CreateCommand extends Command { async run() { const {flags} = this.parse(CreateCommand) if(!flags.title) { console.log("Error: Missing post title, please use the --title flag") return 1 } if(!flags.body) { console.log("Error: Missing content for the new post, please use the --body flag") return 1 } if(!flags.username) { console.log("Error: Missing username, please use the --username flag") return 1 } if(!flags.password) { console.log("Error: Missing password, please use the --password flag") return 1 } let newPost = flags let C = new wpClient(config.get("wp-api.url"), config.get("wp-api.endpoints")) C.CreatePost(newPost, (err, resp) => { if(err) return console.error(err) const view = new V(resp) view.run() }) } } CreateCommand.flags = { username: flags.string({ description: 'Your username' }), password: flags.string({ description: 'Your password' }), title: flags.string({ description: 'The title of the new blog post' }), body: flags.string({ description: 'The content of the new blog post' }) } module.exports = CreateCommand

You can check the full repository to review the other commands, but you’ll see the same pattern repeating over and over. One could argue, from the design point of view, that this could’ve all been handled by a single file, but since we’re keeping things simple here, I like to leave room for customization. And having a single file per command allows for that.

As you can see, the code doesn’t do anything special, it basically does four things:

Defines the new command class and its flags. This is useful for OClif, since it can use that information for auto-generating help commands.

Do some basic flag validation, in order to make sure we’re providing all the required pieces of information. If one of them is missing, we’ll bail out and show the error message.

Call the API client with the correct method.

And finally, send the response to the View class, which will take care of outputting this in whatever format we require.

That’s it! There is no black magic at work. We’re simply interacting with a (very well defined) REST API to deal with our posts without having to go through WordPress’ UI. Neat!

The View Files

These are very basic classes, they’re here for the same reason I created a single file per command, to allow for growth. As you can see in the full repository, these classes don’t really do much right now. But if you wanted to, you could add quite a lot of code to increase the quality of the presentation.

There is a lot you can do in the console to add extra flares and you would do that from within these files, without having to touch any of the main logic behind the client. That’s the only reason why I’ve coded these classes as separate entities.

Here is an example of one of them, in fact, this one is the most complex of the lot:

module.exports = class ListView { constructor(items) { this.items = items } async run() { let posts = this.items.map( i => { return { id: i.id, title: i.title.rendered, last_modified: i.modified } }) posts.forEach( p => { console.log("* (", p.id, ") ", p.title, " [", p.last_modified, "] ") }) } }

The API Client

Last, but certainly not least, the API client class is the one that has the responsibility of centralizing all communications with the API. As you’ll see in a bit, that translates into setting up different requests, based on the action at hand, and performing them through the request module. That’s about it.

Here is the full code for you to read:

const R = require("request") function Base64(txt) { return new Buffer.from(txt).toString("base64") } module.exports = class WordPressClient { constructor(url, endpoints) { this.apiUrl = url this.endpoints = endpoints } getSecureHeaders(usr, pwd) { return { Authorization: 'Basic ' + Base64(usr + ":" + pwd), 'accept': 'application/json' } } makeRequest(options, cb) { options.json = true //force the json data type on all requests return R(options, (err, resp, body) => { if(err) return cb(err) if(resp.data && resp.data.status >= 400) { return cb({ errorMsg: resp.data.message }) } if(typeof body == 'string') { if(body.indexOf("|") == 0) body = body.replace("|", "") body = JSON.parse(body) if(body.data && body.data.status >= 400) { return cb({ errorMsg: body.message }) } } cb(err, body) }) } async DeletePost(data, cb) { const URL = this.apiUrl + this.endpoints.delete.replace(":id", data.postId) let headers = this.getSecureHeaders(data.username, data.password) let options = { url: URL, headers: headers, method: 'DELETE' } return this.makeRequest(options, cb) } async PublishPost(data, cb) { const URL = this.apiUrl + this.endpoints.update.replace(":id", data.postId) let headers = this.getSecureHeaders(data.username, data.password) let update = { status: 'publish' } let options = { url: URL, headers: headers, body: update, method: 'POST' } return this.makeRequest(options, cb) } async CreatePost(data, cb) { const URL = this.apiUrl + this.endpoints.create let headers = this.getSecureHeaders(data.username, data.password) let newPost = { title: data.title, content: data.body, excerpt: data.body } let options = { url: URL, headers: headers, body: newPost, method: 'POST' } return this.makeRequest(options, cb) } async GetPost(id, cb) { const URL = this.apiUrl + this.endpoints.get.replace(":id", id) let options = { method: 'GET', url: URL } return this.makeRequest(options, cb) } async listPosts(cb) { const URL = this.apiUrl + this.endpoints.list let options = { method: 'GET', url: URL } return this.makeRequest(options, cb) } }

As you can see the only relevant method here is makeRequest , the rest of them are simply using that one.

And that is it for the implementation of this client!

Although it is quite simple, it should be enough to prove the potential that a headless CMS, in particular, a headless WordPress provides. We could take this code and turn it into a SPA application. We also could integrate this command line tool into another Desktop tool just as easily, thus adding extra capabilities without the need to go and login into WordPress where you'll be limited by the constraints added by its UI.

The Drawbacks of Headless Wordpress

I still wanted to cover some minor drawbacks related to using a headless version of WordPress.

This is not really Headless CMS

I know I’ve been saying “Headless WordPress” for the better part of 10 minutes now, and the article is titled that way too, but if we want to get technical for a minute (he says while pushing back his fake glasses with his index finger), this is actually a Decoupled WordPress.

The main difference between a headless CMS and decoupled CMS such as this one is that whilst we do have that beautiful API with all the benefits I mentioned before, the head, if you will, is still attached to the body. In other words: we have to deal with the UI, at least in the beginning while installing the main plugins and performing the basic setup.

And even worse, if we do want to go full headless after that, we have a bunch of code laying around, installed but never used again (the CMS interface).

You have the building blocks and that’s it

WordPress’ API is great, don’t get me wrong, it provides all you need to do whatever you want with it. But then again, so does the Assembler language, and you don’t see many developers using it. What I’m trying to say here is: if what you want is easy access to a Headless CMS that lets you pull data from an API and publish is the way you want, maybe going with this solution is too low level.

There are many great alternatives out there, such as ButterCMS, that let you build exactly the same thing with higher level tools. These types of companies create, publish and maintain SDKs that allow you to stay away from thinking about APIs and HTTP requests and simply think about blog posts and pages. All you have to do is tell your code where to render them.

(See how Butter compares to Wordpress)





Plugin dependency

Although not a huge issue if you’re running a local version of WordPress, it’s quite a big deal if you’re on their managed platform. Because as I mentioned at the beginning of this article, you have to pay the highest price in order to be able to install plugins. This might even be a blocker for some of you who are not planning nor willing to spend money on your particular project.

Additionally, even though I wouldn’t consider it a drawback, it’s definitely an inconvenience to have to install plugins in order to be able to access the API and use a specific authentication method.

These types of features for a headless system are available right out of the box, and what’s even better: they are supported by the group/organization that created the platform (as opposed to being a 3rd party plugin developed by anyone willing to do it).

Conclusion

Regardless of the drawbacks one might find in this headless version of WordPress, the truth is this is a very good way to start migrating into a headless CMS. With this approach, you have the full power of the REST API at your disposal. You can try to use it however you'd like, but at the same time you have the safety net that is the actual web UI for WordPress, where you can log in and do any content management you might need.

If you play your cards right and abstract the integration with WordPress’ API correctly, you can then take your content and migrate it into other systems for whatever reasons you might have (be it monetary, technical or even personal reasons).

With the current trend of technology, there are constantly new devices coming up, devices that have the ability to, not only connect to the internet, but to display multimedia content (that is how we old people call images, videos and text by the way) in very different ways. Given that, headless content management systems (such as WordPress’ API and others like ButterCMS) are literally helping pave the way to the future and by using them, you’re simplifying the future work required to be compatible with whatever new medium might appear.

In the end, the choice of platform is up to you depending on your personal needs and technical mastery. There are dozens of options when it comes to headless systems (some of them will be compatible with your tech stack, others might need extra work for you to use them) and quite a lot of normal CMS to pick from as well.

Whatever you do, make sure to leave a comment below and tell us what you think about these headless platforms and if you’re using or thinking about using one, which one is it?