WP Rest API
WP Rest API
Cal Evans
This book is for sale at http://leanpub.com/using_the_wordpress_rest_api
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.
Register . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Foreword . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Making WordPress Work Harder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Additional Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Thank you . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Cal Evans
Hi!
Before you dive in, I just wanted to say thinks for being a reader. I have a few add-on videos planned
for this book and would love to tell you about them when they are released.
If you want to stay up-to-date, Click Here and fill out the form.
I do not ever share your email address with anyone, ever.
This will be a very low-volume mailing list. Probably one email a month. But since you are interested
in the WordPress REST API, I want to make sure I can reach you when I add new content.
I hope you like the book!
Cheers! :)
=C=
Cal Evans
p.s. Feedback and testimonials are always welcome. Email me, [email protected]. See, I shared my
email address with you. :)
May the following chapters help you to recognize the potential that exists with WordPress and
empower you to write applications that you didn’t think were possible with a platform that powers
over a third of the internet.
Tom McFarlin
@tommcfarlin
https://tommcfarlin.com
Introduction
Dear Reader,
I didn’t start out with the intention of writing a book on the WordPress REST API; like most of my
projects, I started out with an idea. I wanted to add some functionality to my podcast’s website.
Approximately 10 hours later, I realized I had three browser windows open and twenty-seven tabs
open across them, all about the WordPress REST API.
I sat back and began to think. I’ve been programming for 36 years. During that time, the most
important skill I’ve honed across all languages I’ve worked in, is the ability to find information, and
figure things out. I realize other developers may not have developed this skill to the level I have or
just may not be as tenacious as I am when I have a project in mind.
Working with the WordPress REST API should not require this much effort. Honestly, once you do
find all the pieces and begin assembling them, you begin to understand it really isn’t difficult to
work with.
I’ve collected everything I learned about the WordPress REST API here in this one book. Everything
you need to know to manipulate posts, images, taxonomies, users, and everything else is covered
here.
I hope this book saves you hours of research and helps you get to the fun part more quickly, making
WordPress do cool things.
Until next time,
I <3 |<
=C=
Cal Evans
June 24, 2019
West Palm Beach, Florida, USA
Introduction to the WordPress REST
API
In this chapter, we lay the groundwork for the rest of the book. If you know what an API is, if
you understand a little about REST, and you are confident in your abilities, it is safe to skip it. If,
however, you’ve not done any work with an API before, take the time to read this if only so that
you understand what you are diving into.
Throughout its history, developers have sought ways to extend the usefulness of WordPress in
various ways. Once plugins were introduced in version 1.2, developers had an official way of doing
some tasks, but others simply did not fit in the model. Version 4.7 gave us the first REST endpoints
to
allow for machine-readable external access for enhanced third-party interaction.
This was a huge improvement, even in its early stages. Since then, WordPress’ REST API has grown
more powerful with each release. The integrations that are now possible simply were not available
before. Many plugins now define their own REST API endpoints to extend their usefulness to
developers.
Tasks that used to require code to be written and executed outside of WordPress to access the
raw database and manipulate WordPress data can now be accomplished by making calls to REST
endpoints and working within the system.
Many WordPress systems can happily exist without ever having knowledge of the REST API. Going
forward, however, all developers who extend WordPress on behalf of their projects or clients will
have to know how the REST API operates and how to make calls to it to manage all aspects of a
WordPress based system.
Creating a well known and standardized way for applications to talk to each other allows developers
to build systems that are more valuable than their individual parts.
In this book, we cover what developers can do using the standard REST API endpoints that are baked
into all WordPress installations. We will manipulate :
• Posts
• Media
• Users
• Tags
• Pages
• Comments
• Taxonomies
• Post Types
• Post Statuses
• General Blog Settings
Once you understand the basics of how to use the WordPress REST API, understanding how to use
the other endpoints is simply a matter of syntax and parameters.
1 $ command-to-be-executed --parameters
This is the standard shell prompt for Linux and macOS. If you are using Windows and you are on
Windows 10, it is highly recommended you install the Windows Linux Subsystem. This will allow
you to use see the same format as above.
It is possible to run the examples from the Windows command line like this.
It is up to you to make sure the necessary software is properly installed, and the command is called
in the proper format.
Summary
This chapter discussed what an API is, what REST is, and why WordPress now comes with a REST
API. We delved into a little of what you can do with the WordPress REST API. Finally, we discussed
the requirements for running the examples in this book. This chapter helped introduce you to the
concepts; it is now time to put those concepts into action.
Reading Public Endpoints - Posts and
Pages
Welcome to chapter two! In chapter one, we talked about the basic concepts of the WordPress REST
API. In chapter two, we start seeing the magic behind the concepts. We’ll start exploring some of
the things we can do with the WordPress REST API which will work for any WordPress install that
has the REST API implemented.
In this chapter, we are going to discuss the following topics:
To see the metadata the WordPress REST API will show you about a given WordPress install, access
the /wp-json endpoint. This is the most basic of endpoints.
The first six pieces of information are the basics about the given WordPress install. Anyone familiar
with setting up a WordPress install will recognize this information from the Admin->General->Settings
page.
Namespaces
After this, we are given a list of the namespaces installed. In WordPress REST API installs, a
“namespace” is the root path of an API. While there are no hard and fast rules as to how namespaces
are structured, the best practice recommended by the WordPress REST API Handbook is the format
vendor\version. As you can see from our example, the exact definition is left up to the vendor.
1 "namespaces": [
2 "oembed/1.0",
3 "wp/v2"
4 ],
The oEmbed API uses a version of major.minor, while the official API endpoint uses “v”+version.
Either version works. The version number is important because if a vendor changes an endpoint,
they will usually version their API. This way calls that would normally fail, will automatically fail,
until the developer updates the call and the URL being accessed.
By default, these are the only two REST APIs installed when you create a basic WordPress install.
Adding additional plugins like Akismet, will add new namespaces to this list.
Namespaces in the WordPress REST API are different in structure and meaning from namespaces
in PHP or other programming languages. Don’t get confused by assuming they are the same thing.
Routes
Namespaces plus endpoints make up “routes.” For example, if your WordPress install is at https://example.com,
and your namespace is eicc/v1/podcast and your route is “episode, then the entire URL to your
endpoint is https://example.com/wp-json/eicc/v1/podcast/episode. Note the wp-json after the
domain name. This is where all the WordPress REST API endpoints exist, both native and custom.
The route is the custom part of the endpoint. In the case above, the namespace is my company name
+ this plugin’s namespace + the version for this plugin. This allows me to create multiple plugins
under the eicc namespace, version each one, and have different endpoints for each one.
Reading Public Endpoints - Posts and Pages 11
The combination of all of this is an endpoint. The route portion of the endpoint is the combination
of the pieces above. From this point on, unless we are discussing a piece of the route individually,
we will assume to endpoint to mean the entire URL.
Our listing of the wp-json endpoint above gave us several routes. All the routes given fall under one
of the listed namespaces.
1 "routes": {
2 "/": {
3 "namespace": "",
4 "methods": [
5 "GET"
6 ],
7 "endpoints": [
8 {
9 "methods": [
10 "GET"
11 ],
12 "args": {
13 "context": {
14 "required": false,
15 "default": "view"
16 }
17 }
18 }
19 ],
20 "_links": {
21 "self": "http://example.com/wp-json/"
22 }
23 },
This is a listing for the endpoint https://example.com/wp-json, the one we accessed. From this we
can tell the following:
• There is only one route, /. This means that https://example.com/wp-json/ is valid, but
https://example.com/wp-json/4 is not.
• There is no namespace assigned to this route.
• There is only one method that is allowed to access this endpoint, GET. If you try to POST to
this endpoint, for instance, you will receive an error.
• There are no required parameters to access this endpoint.
• This endpoint only allows for one argument (defined in the args collection) “context,” and the
default value for this argument is “view.”
• The link to this endpoint is listed in _links.self.
Reading Public Endpoints - Posts and Pages 12
You will get one entry in the routes collection for each endpoint registered. They are grouped by
namespace.
This collection of information will help you see what REST API endpoints are available to you
and what parameters each endpoint takes. While we are reading this information and digesting it
ourselves, it would be trivial to write code that consumes this page, determines if the endpoint it is
looking for exists on this WordPress install, and if so, take action. APIs are, after all, for computers
and not for humans.
All namespaces in the WordPress REST API will output their own subset of the above data if you
access / with the verb GET. For example, if you access https://example.com/wp-json/oembed/1.0/ it
will emit just the endpoints, arguments, and constraints for the oEmbed REST API.
Listing Posts
Since posts are the most ubiquitous types in WordPress, we will start our experimenting with the
WordPress REST API by listing and retrieving posts. This is also the simplest example of use of the
API.
We know from the previous section, if we leave posts off then we will get the metadata for the
namespace. Adding posts to it tells WordPress we are now interested in talking to it about posts.
The metadata tells us this endpoint will accept two verbs GET and POST.
Do not confuse the HTTP verb POST with the WordPress concept of posts.
• GET will retrieve a listing of posts. The number of posts retrieved by default is controlled by
the Admin setting in the WordPress install, however, this number can be overridden with a
parameter.
• POST will allow us to actually create a new post in this WordPress installation. We will go into
more detail about that in the coming chapters. Before we can do that, we need to understand
concepts like authentication and authorization; we’ll get to that.
For now, we will use the command above to retrieve a list of posts. Let’s unpack the results.
Reading Public Endpoints - Posts and Pages 13
1 {
2 "id": 200,
3 "date": "2019-06-01T14:45:20",
4 "date_gmt": "2019-06-01T14:45:20",
5 "guid": {
6 "rendered": "http://example.com/?p=200"
7 },
8 "modified": "2019-06-01T14:45:20",
9 "modified_gmt": "2019-06-01T14:45:20",
10 "slug": "using-the-wordpress-rest-api",
11 "status": "publish",
12 "type": "post",
13 "link": "http://example.com/2019/06/using-the-wordpress-rest-api/",
14 "title": {
15 "rendered": "Using the WordPress REST API"
16 },
17 "content": {
18 "rendered": "<h1>The complete HTML of the post as stored in the data sto\
19 re, sans the theme.",
20 "protected": false
21 },
22 "excerpt": {
23 "rendered": "The first paragraph of the content unless an excerpt was se\
24 t on the post.",
25 "protected": false
26 },
27 "author": 1,
28 "featured_media": 0,
29 "comment_status": "closed",
30 "ping_status": "closed",
31 "sticky": false,
32 "template": "",
33 "format": "standard",
34 "meta": [],
• modified and modified_gmt are similar in nature to the above date fields except their value
is the date and time the post was last modified.
• slug is a unique-to-this-installation identifier for this particular post. Permalinks uses this as
the identifier of the post.
• status is the status of the post. This is usually publish or draft but future, pending, and private
are also valid values.
• type is the post type for this post. If this WordPress installation has custom post types, this
value can be any of the custom post types, the default is post.
• link is the link to the post honoring the PermaLink rules if any.
• title is a collection with a single element, rendered. This is the title of the post.
• content is the full content of the post. If this is a standard WordPress post, then this is the body
of the post including all HTML. This does not include anything that is in the template for the
post, only the content you see displayed in the editor.
• excerpt If an excerpt for the post was set, then this is that content. Otherwise, this is the first
paragraph of the post.
• author This is the ID of the author of the post.
• featured_media This is the post ID of the featured media for this post or 0 if none.
• comment_status tells you whether or not comments can be posted to this post. Valid values
are open and closed.
• ping_status Similar to above, this field tells you whether or not pingbacks to this post will be
recorded. Valid values are open and closed.
• sticky This field is true if this post is a sticky post and false if it is not.
• template is the name of the theme file to use to render this page or empty string if none is
specified.
• format This is the format for this post. The default value is standard but aside, chat, gallery,
link, image, quote, status, video, and audio are also acceptable values.
• meta is a collection of metadata associated with the post or empty collection if none. Note, this
collection will not contain any and all metadata associated with the given post, only registered
metadata.
All of these fields will be returned for each post returned. We will talk about pagination in the section
below about global variables. Right now, all you need to know is the number of posts returned is the
default set by the blog Admin in the Settings->Read page. You can override that number and you
can also specific pages of posts from this endpoint.
The payload returned is very similar to the one above with the exception that you only get a single
post returned, not a collection of posts.
The other major difference between requesting a single post and a collection of posts is if you do
not have authorization to view the requested post, you will receive a 401 error. You may not have
authorization for a variety of reasons including the following.
• The post is not a public post, and you are not logged in.
• The post requires a password to access.
Since we are experimenting with public endpoints at the moment, if any of these are true, we will
receive a 401 error. Additionally, if we request a post that does not exist, or a post that exists but has
not yet been published, we will receive a 404 error.
Pagination
As with almost any API that returns a collection, WordPress gives developers control over the
content of the list via pagination controls. The WordPress REST API gives developers the standard
controls of number of entries per page and page to display. These parameters are page and
per_page respectively. These parameters are passed in on the URL.
This will return a collection of posts which is per_page long. The collection will begin at post page
* per_page. Both parameters must contain a positive integer value. per_page has a maximum value
of 100. Any value for either parameter that is out of bounds will result in a response code 400.
• context: The possible values for this are view, embed, edit. The default is *8view**. Context
determines which fields are returned in the response payload.
• page, per_page: See section above on pagination.
Reading Public Endpoints - Posts and Pages 16
• search: Adding search and a word or a phrase, will limit the results set to entities that contain
that word or phrase somewhere in the post, title, or excerpt.
• before, after: If you specify either of these parameters and a ISO8601 formatted date as
its value, the resulting collection will only contain entities published after the date speci-
fied. For after to work you have to specify the full and correctly formatted ISO8601 date.
YYYY-MM-DDTHH:II:SS. You cannot use one of the abbreviated formats.
• author, author_exclude: If you specify the 8author parameter and then a valid author ID, the
resulting collection will only contain entities from that author. author_exclude is the logical
opposite.
• exclude: If you pass in exclude as a command line parameter, the value can be a single entity
ID (e.g., post ID, page ID, etc.) or a comma-delimited list of entity IDs. These entities will be
excluded from the resulting collection.
• include: Similar to exclude but if present then the collection will only contain the entities
specified.
• offset: This is mainly used in pagination. Instead of specifying page and letting the system
computer page * per_page, you can specify the offset to use.
The command above will result in a collection of 20 posts, starting at the 120th most recent post.
• order: Valid values are DESC and ASC. By default, all collections are ordered in DESCending
order of date published. The above example counts backward from the most recent post and
starting with 120, builds the collection. If you specify ASC, then it will start with the first post
in the system and count forward.
• orderby: The default order for any collection is date. If you want to change that, you can
specify another field to order on. Valid values are:
– author
– date
– id
– include
– modified
– parent
– relevance
– slug
Reading Public Endpoints - Posts and Pages 17
– title
• slug: You can specify the slug of the post you want retrieved. This has to be an exact match. A
partial slug will not return all matches; it will return an empty collection.
• status: You can filter on the status of the entity. The default is publish. Any valid status is
accepted. If you pass in a status with no entities in that status, you will receive an empty
collection. If you pass in a string that is not a valid status, you will receive a 400 error. If
you specify a status which requires authorization to view and you are not authenticated and
authorized (we cover that in a coming chapter), then you will receive a 400 error.
• categories, categories_exclude: Two sides of the same coin, you can either specify a comma-
delimited list of categories to include, or exclude.
• tags, tags_exclude: A tag ID or comma-delimited list of tag IDs on which to filter the
collection. All entities in the collection will contain one of the specified tags. tags_exclude
is the logical opposite. No entity will be included in the collection if it contains one of the
specified tags.
• sticky: Limits the collection to only items that have been marked as sticky.
• /wp/posts/
– GET Retrieve a collection of posts
– POST Create a new post
• /wp/posts/#ID#/
– GET Retrieve a single post
– POST, PUT, PATCH Update a single post
– DELETE Delete a single post
• /wp/posts/#ID#/revisions
– GET Retrieve a collection of post’s revisions
• /wp/posts/#ID#/autosaves
– GET Retrieve a collection of post’s autosaves
– DELETE Delete the autosaves for a given post
• /wp/posts/#ID#/autosaves/#AUTOSAVEID#
– GET Retrieve a single autosave of a post
Reading Public Endpoints - Posts and Pages 18
Page Endpoints
WordPress implements the same endpoints for pages as it does for posts. The access methods are very
similar as well as the payloads. We will not rehash access methods or payloads here since they are
so similar. We will, however, provide a list of the endpoints for reference. Not all of these endpoints
are publicly available. Most need authentication and authorization before they can be called.
• /wp/pages/
– GET Retrieve a collection of pages
– page Create a new page
• /wp/pages/#ID#/
– POST, PUT, PATCH Update a single page
– DELETE Delete a single page
• /wp/pages/#ID#/revisions
– GET Retrieve a collection of page’s revisions
• /wp/pages/#ID#/autosaves
– GET Retrieve a collection of page’s autosaves
– DELETE Delete the autosaves for a given page
• /wp/pages/#ID#/autosaves/#AUTOSAVEID#
– GET Retrieve a single autosave of a page
Summary
We have examined some of the main things you can do with the WordPress REST API without
authentication and authorization. It is important that before you move forward, you understand
the basics of retrieving information. All of the endpoints discussed here can be accessed on any
WordPress installation that has not disabled REST. Before moving forward, you are encouraged to
explore. See what you can accomplish just with these endpoints.
Reading Public Endpoints - Everything
Else
In the last chapter, we focused on the two main public endpoints, posts and pages. A standard
WordPress install, however, has other public endpoints that can be accessed. This chapter describes
some other endpoints you can access. By now you should have set up a test WordPress install you
can practice on. If not, these endpoints are read-only, so it won’t hurt to access them on a production
system. That having been said, this is the last chapter you will be able to work through without an
install to which you can make changes.
In this chapter, we will discuss the following topics.
• Taxonomy
• List Categories
• List Tags
• Global parameters
• Using the oEmbed endpoint
Taxonomy
In its most simple definition, taxonomy is how we classify things in WordPress. In a standard
WordPress installation, there are two types of taxonomy: categories and tags. Categories tend to
be broad classifications, and tags tend to be more narrowly focused. There, however, is no right or
wrong way to use these taxonomies so your usage may differ.
The WordPress REST API gives us endpoints for manipulating standard taxonomies, and they are
described for us in the call to /wp-json.
Both of these have similar endpoints. We discuss the collection endpoint for each as they are available
without authentication and authorization. Here is the complete list of the categories endpoints; tags
has the exact same list.
• /wp-json/wp/v2/categories
– GET returns a collection of categories.
– POST creates a new category.
• /wp-json/wp/v2/categories/#ID#
– GET retrieves a single category.
– POST, PUT, and PATCH will update an existing category.
– DELETE removes a category form the system.
Reading Public Endpoints - Everything Else 20
List Categories
WordPress also provides REST API endpoints to query categories. The categories endpoint returns
a collection:
Below we see a single category payload to show what this endpoint returns.
1 [
2 {
3 "id": 268,
4 "count": 3,
5 "description": "",
6 "link": "https://example.com/category/periscopes/",
7 "name": "Periscopes",
8 "slug": "periscopes",
9 "taxonomy": "category",
10 "parent": 0,
11 "meta": [],
12 "_links": {
13 "self": [
14 {
15 "href": "https://example.com/wp-json/wp/v2/categories/268"
16 }
17 ],
18 "collection": [
19 {
20 "href": "https://example.com/wp-json/wp/v2/categories"
21 }
22 ],
23 "about": [
24 {
25 "href": "https://example.com/wp-json/wp/v2/taxonomies/category"
26 }
27 ],
28 "wp:post_type": [
29 {
30 "href": "https://example.com/wp-json/wp/v2/posts?categories=268"
31 }
32 ],
33 "curies": [
Reading Public Endpoints - Everything Else 21
34 {
35 "name": "wp",
36 "href": "https://api.w.org/{rel}",
37 "templated": true
38 }
39 ]
40 }
41 }
42 ]
Beyond the normal information we as developers are used to seeing about categories, we see the
endpoint returns a _links collection. Almost all WordPress REST APIs return _links. This collection
is a collection of related endpoints of interest which are related to the payload returned.
• All _links payloads have the attribute self. self is the link necessary to access the element
being described. In the case above, self is a link to the category “periscopes,” which is part of
a collection.
• collection is a link to fetch the collection of this type of item, in our case, categories. If we had
called the single category endpoint, collection would give us access to the entire collection.
• about is a link that will give information about the type of item being displayed. In this case,
since we asked for the collection of categories, the about gives a link to an endpoint that
describes what a category is.
• wp:post_type is a call to the posts endpoint described above with the parameter “categories”
and the value of the category being displayed. This will return a collection of all the posts
tagged with this category.
• curies are part of the HAL specification. They are supposed to be a link to additional
information. Here is a great article by Mike G. Stowe titled What the Heck are CURIEs?!.
Sadly, it does not look like this is operational at w.org. In our case, the URL would be
https://api.w.org/categories to get an official definition of a category. However, that link
brings up a 404 as of this writing.
List Tags
Similar to categories, tags has its own endpoint.
The above call will return a collection of tags. The payload is also similar to categories. We won’t
take the time to discuss each item, see the discussion above.
Reading Public Endpoints - Everything Else 22
1 {
2 "id": 292,
3 "count": 1,
4 "description": "",
5 "link": "https://example.com/tag/my-tag/",
6 "name": "my-tag",
7 "slug": "my-tag",
8 "taxonomy": "post_tag",
9 "meta": [],
10 "_links": {
11 "self": [
12 {
13 "href": "https://example.com/wp-json/wp/v2/tags/292"
14 }
15 ],
16 "collection": [
17 {
18 "href": "https://example.com/wp-json/wp/v2/tags"
19 }
20 ],
21 "about": [
22 {
23 "href": "https://example.com/wp-json/wp/v2/taxonomies/post_tag"
24 }
25 ],
26 "wp:post_type": [
27 {
28 "href": "https://example.com/wp-json/wp/v2/posts?tags=292"
29 }
30 ],
31 "curies": [
32 {
33 "name": "wp",
34 "href": "https://api.w.org/{rel}",
35 "templated": true
36 }
37 ]
38 }
39 },
Reading Public Endpoints - Everything Else 23
Global parameters
Throughout this chapter, as we discuss an endpoint, we have given the verbs available, as well as
the arguments it will accept. There are also a series of global parameters available for all of the
endpoints. Let’s take a look at them and how they affect the result sets.
_envelope
There are clients - mostly older clients - that do not allows access to the entire response. The
WordPress REST API allows you to specify _envelope as a parameter on the URL; it requires no
value with it. When you pass this parameter, all the response data, the response code, and the headers
are encapsulated in a collection named body.
This is a parameter that is normally not necessary.
_method
Some older clients may not allow developers to specify HTTP verbs other than GET and POST. In
these rare cases, the WordPress REST API provides a parameter to allow the developer to specify
what VERB was intended.
1 $ curl -v \
2 -H "Content-type: application/json" \
3 -H "Accept: application/json" \
4 -H "Authorization: Bearer $TOKEN" \
5 -d ='{"name":"test"}' \
6 -X GET
7 http://example.com/wp-json/wp/v2/tags?_method=POST
The above call - assuming you have authentication and authorization which we cover in the next
chapter - will create a tag in the system named “test.” The above call forces cURL to issue a GET with
the line -X GET but passes in the parameter _method to tell the WordPress REST API to treat it as a
POST. A new tag will be created if one does not already exist because that is the action registered
for a POST on that endpoint.
This is a parameter that is normally not necessary.
_jsonp
JSONP is an old-style attempt to bypass CORS and allow an application to load data from another
domain. It does this by using the <script> tag to load in data. (Or, more recently, jQuery’s
getScript()). The problem is if you use one of these methods to load data, JavaScript will attempt
Reading Public Endpoints - Everything Else 24
to compile whatever is returned. Since an object cannot compile into code, JavaScript will throw
an error. By passing in the _jsonp parameter, along with a callback function name, the WordPress
REST API will wrap the return payload in a call to the function name supplied.
For example, a call to fetch a single tag like this:
1 /**/
2 parseResponse (
3 {
4 "id": 8,
5 "count": 0,
6 "description": "",
7 "link": "http://example.com/tag/test/",
8 "name": "test",
9 "slug": "test",
10 "taxonomy": "post_tag"
11 }
12 )
Notice the entire response is wrapped in a call to parseResponse(). It is up to you to make sure
the callback function is accessible in your code. You can name your callback any legal JavaScript
function name; there is nothing magical about the name parseResponse. Additionally, it can be as
simple as the example below, which simply returns the data ready for you to manipulate it, or you
can do pre-processing and validation in your callback.
1 function parseResponse(data)
2 {
3 return data;
4 }
JSONP is not normally used anymore and is included in the WordPress REST API to facilitate access
from legacy systems. It is now possible to control access to cross origin resource sharing, and for
security reasons, this is the preferred method.
This is a parameter that is normally not necessary.
_embed
This one is a bit more useful. Passing _embed on the URL will create another element in the payload
collection named _embedded in each element in the collection. If you are requesting a collection of
posts, each post item will have an _embedded element.
Reading Public Endpoints - Everything Else 25
The _embedded element is a collection. This collection contains full elements for each item in the
parent collection that specified an ID. For example, if you specify _embed when requesting a post,
you still get the element author with the author’s user ID as the value. However, in the _embed
collection, you will also have an element author that will contain the full user collection for the
author of the post.
Here is an example. This is only a partial payload.
1 _embedded": {
2 "author": [
3 {
4 "id": 1,
5 "name": "Cal Evans",
6 "url": "https://blog.calevans.com",
7 "description": "Author of the book 'Using the WordPress REST API'",
8 "link": "http://example.com/author/calevans/",
9 "slug": "calevans",
10 "avatar_urls": {
11 "24": "http://1.gravatar.com/avatar/d6d504bf40dea91cd33027e5bb85f7df?s=24&\
12 d=mm&r=g",
13 "48": "http://1.gravatar.com/avatar/d6d504bf40dea91cd33027e5bb85f7df?s=48&\
14 d=mm&r=g",
15 "96": "http://1.gravatar.com/avatar/d6d504bf40dea91cd33027e5bb85f7df?s=96&\
16 d=mm&r=g"
17 },
18 "_links": {
19 "self": [
20 {
21 "href": "http://example.com/wp-json/wp/v2/users/1"
22 }
23 ],
24 "collection": [
25 {
26 "href": "http://example.com/wp-json/wp/v2/users"
27 }
28 ]
29 }
30 }
31 ],
As you can see, this will make your response payloads much larger in most cases. However, it is
designed to reduce the number of calls a developer has to make to the system to gather the data
they need. If you regularly make multiple calls to the system to retrieve objects that are referenced
Reading Public Endpoints - Everything Else 26
in your original call, and you are working on a platform where bandwidth and memory constraints
are not an issue, then it is recommended you specify _embed.
1 curl -s http://www.flickr.com/services/oembed/?format=json&url=https://www.flick\
2 r.com/photos/calevans/286000639/ | jv.sh
1 {
2 "type": "photo",
3 "flickr_type": "photo",
4 "title": "7 of Clubs",
5 "author_name": "CalEvans",
6 "author_url": "https://www.flickr.com/photos/calevans/",
7 "width": 465,
8 "height": "640",
9 "url": "https://live.staticflickr.com/119/286000639_84220944e3_z.jpg",
10 "web_page": "https://www.flickr.com/photos/calevans/286000639/",
11 "thumbnail_url": "https://live.staticflickr.com/119/286000639_84220944e3_q.jpg",
12 "thumbnail_width": 150,
13 "thumbnail_height": 150,
14 "web_page_short_url": "https://flic.kr/p/rgQ2v",
15 "license": "All Rights Reserved",
16 "license_id": 0,
17 "html": "<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/cale\
18 vans/286000639/\" title=\"7 of Clubs by CalEvans, on Flickr\"><img src=\"https://liv\
19 e.staticflickr.com/119/286000639_84220944e3_z.jpg\" width=\"465\" height=\"640\" alt\
20 =\"7 of Clubs\"></a><script async src=\"https://embedr.flickr.com/assets/client-code\
21 .js\" charset=\"utf-8\"></script>",
22 "version": "1.0",
23 "cache_age": 3600,
24 "provider_name": "Flickr",
25 "provider_url": "https://www.flickr.com/"
26 }
Reading Public Endpoints - Everything Else 27
As you can see, this gives developers all the data they need to embed the picture into a document.
If you have ever pasted a link to a YouTube video into the classic WordPress editor and it replaced
it with the thumbnail of the video, the oEmbed specification is how that is done.
WordPress includes an oEmbed endpoint for anyone wanting to get embeddable information on a
public post.
1 curl -s http://example.com/wp-json/oembed/1.0/embed?url=http://example.com/?p=20\
2 0 | jv.sh
Will give a payload with all the information necessary to create an embed of the post in another
document.
1 {
2 "version": "1.0",
3 "provider_name": "Cal's Test Site",
4 "provider_url": "http://example.com",
5 "author_name": "Jane Doe",
6 "author_url": "http://example.com/author/janedoe/",
7 "title": "Interview with Kris Kringle",
8 "type": "rich",
9 "width": 600,
10 "height": 338,
11 "html": "<blockquote class=\"wp-embedded-content\"><a href=\"http://example.com/\
12 2019/06/interview-with-kris-kringle/\">Interview with Kris Kringle</a></blockquote>"\
13 ,
14 "thumbnail_url": "http://example.com/wp-content/uploads/2019/05/302.jpg",
15 "thumbnail_width": 400,
16 "thumbnail_height": 400
17 }
We have the name of the post, the name of the author, the content of the post, and a thumbnail of
the graphic. Using this information, we could easily create anything from a callout to a graphic with
a link to the actual post. Most importantly, the html field contains code that can be inserted into an
HTML document on the same site, as is.
The WordPress oEmbed REST API will return data on any public content that is stored in the posts
table. This means pages, posts, custom post types, and media will all return an oEmbed payload.
The WordPress oEmbed REST API takes three parameters.
• url: This is the only required parameter. You have to pass in the full URL of the item you want
to embed. If it is not public, if it is not valid, or if it is not a URL for this site, you will be
returned an error.
Reading Public Endpoints - Everything Else 28
• format: The oEmbed API will return its payload in two different formats, JSON or XML. JSON
is the default if you do not specify a format. If JSON is your preferred format, there is no need
to specify this format.
• maxwidth: If the item you are requesting an embeddable for is an image, you can specify the
maxwidth parameter. This will resize the graphic to the proper size.
Summary
For the last two chapters, we have discussed and used the WordPress REST API endpoints you can
use without logging in. This is a very limited subset of the things the WordPress REST API can do.
In the next chapter, we introduce the concepts of authentication and authorization, the two sides of
the auth coin. Understanding this is the key to unlocking all of the possibilities.
Authentication, WordPress, and You
There is only so much a system will let you do without knowing who you are and what you are
allowed to do. WordPress started life as a blogging platform. As a blogging platform, all were allowed
to read, but only those that could prove we knew them could actually post. They had to authenticate
with the system before they were allowed. Later on, WordPress allowed roles to be assigned. Now,
just because you could authenticate with the system, didn’t mean you had permission to actually
write and publish a post; you had to also have authorization.
In this chapter, we will talk a little about the difference between the two. Then, we’ll discuss how
they are implemented in WordPress and how we use them to secure the WordPress REST API.
• Authentication is side one of the auth coin. Does our system know this person at all? Do they
have the proper credentials (user name and password) to authenticate to our system?
• Authorization is side two of the auth coin. Once we know the person has the credentials to
authenticate, we then need to know what permissions they have in the system. This is known
as authorization.
Taken together, they are collectively known as “auth.” It is important when discussing APIs that you
understand the difference and use the full and proper term when having a discussion.
“The user needs auth to execute that task.”
This is an example of bad communication. Is this person saying the user needs authentication to
the system, and that once authenticated, any user can perform the task? Or is this person saying the
user needs the proper authorization before they can perform the task?
Words mean things; be as explicit as possible when having a technical discussion.
Authentication, WordPress, and You 30
1 $username = 'myUserName';
2 $password = 'myPassword';
3 $credentials = base64_encode($username . ':' . $password);
4 header('Authorization: Basic ' . $credentials);
This will work, but it is not a good idea in production. The biggest problem with basic authentication
is that you are constantly passing your username and password over the wire. Even with an
encrypted session (HTTPS), you are still widening the “attack window.”
Basic authentication is fine if you are doing local development and don’t want to spend the cycles
implementing something more secure. However, before you put your system into production, you
are strongly urged to move to a more secure authentication concept.
Authenticating
As stated above, the first step in authenticating is passing in your user credentials. Depending on
what you are doing, this may be your real user or a user you created just for the API. In most cases,
unless you are allowing other users to authenticate and do things they are authorized to do via the
WordPress REST API, it is recommended that you create a user just for the API. Give this user the
Authentication, WordPress, and You 31
minimum permissions you can to allow it to accomplish the tasks that you want. Use the WordPress
password generator to give it a nice long and secure password.
For example, in the companion volume to this book, “Extending the WordPress REST API,” I show
a plugin which creates REST API endpoints to allow me to manipulate posts by episode number. I
create, update, and publish posts, as well as upload media via these endpoints.
To facilitate this, I created a special user account, gave it a secure password, and gave it enough
permissions to accomplish these tasks. This user is never used for anything else. Should I need to,
I can change the password, and then update my software’s config files without affecting any other
user in the system.
NEVER USE YOUR ADMIN CREDENTIALS FOR THE API! If for no other reason than the one
discussed above, passing them around like this opens up a window of vulnerability. If your admin
account is compromised, you have serious problems outside the scope of this book.
1 <?php
2 error_reporting(E_ALL);
3 ini_set('display_errors', 1);
4
5 $username = '';
6 $password = '';
7 $url = 'https://example.com/wp-json/jwt-auth/v1/token';
8
9 $command = 'curl -s '
10 . '-H "Content-type: application/json" '
11 . '-H "Accept: application/json" '
12 . '-d \'{"username":"' . $username .'","password":"' . $password. '"}\' '
13 . '-X POST '
14 . $url ;
Authentication, WordPress, and You 32
Yes, that’s ugly, but it should work on the majority of systems. It was written on Windows 10 using
the WLS. It has been tested using macOS and Linux as well.
If you get a token and a return code of 0, then congratulations, your plugin is properly configured,
and you can move forward. If, however, you get anything else, STOP. Everything else in this book
and the companion volume depends on this plugin being properly configured and working.
As you can see from our test script, we do still pass the username and password in. We only do this
once, though. Our token is good for seven days. We can store the token somewhere and use it until
the expiration date before we have to pass in our username and password again.
The code above strips out the token from the payload because it is all we are interested in. There is
more information returned; the full payload looks like this.
1 {
2 "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC90ZXN0LmNhb\
3 GV2YW5zLmNvbSIsImlhdCI6MTU1OTc1NjEwNSwibmJmIjoxNTU5NzU2MTA1LCJleHAiOjE1NjAzNjA5MDUsI\
4 mRhdGEiOnsidXNlciI6eyJpZCI6IjEifX19.-yessY6DzjkS4fUJF9MTEkKNvZaj5BqmDUinvd6aIpo",
5 "user_email": "[email protected]",
6 "user_nicename": "apiuser",
7 "user_display_name": "API User"
8 }
One piece of information that is missing from the response payload above is the user_id. Thankfully,
the plugin offers developers five hooks that can be registered, one of them being jwt_auth_token_-
before_dispatch. Hooking into this allows developers to modify the response payload before it is
dispatched to the user.
Now that we have the token, all of our requests to the WordPress REST API can include it in a
header.
Authentication, WordPress, and You 33
1 {
2 "code": "jwt_auth_valid_token",
3 "data": {
4 "status": 200
5 }
6 }
1 {
2 "code": "jwt_auth_invalid_token",
3 "message": "Syntax error, malformed JSON",
4 "data": {
5 "status": 403
6 }
7 }
1 <?php
2 error_reporting(E_ALL);
3 ini_set('display_errors', 1);
4
5 $username = '';
6 $password = '';
7 $baseurl = 'https://example.com/wp-json';
8 $token = '';
9
10 $token = getToken($username, $password, $baseurl);
11
12 $command = 'curl -s '
13 . '-H "Authorization: Bearer "' . $token . ' '
14 . '-H "Content-type: application/json" '
15 . '-H "Accept: application/json" '
16 . $baseurl . '/wp/v2/posts?status=draft';
17 exec($command, $return);
18
19 if ( is_array($return) and ! empty($return)) {
20 print_r(json_decode($return[0]));
21 }
22
23 echo "\nDone\n";
24
25 function getToken(
26 string $username,
27 string $password,
28 string $baseurl
29 ) : string {
30 $command = 'curl -s '
31 . '-H "Content-type: application/json" '
32 . '-H "Accept: application/json" '
33 . '-d \'{"username":"' . $username .
34 '","password":"' . $password. '"}\' '
35 . '-X POST '
36 . $baseurl . '/jwt-auth/v1/token';
37 exec($command, $return);
38
39 $payload = json_decode($return[0], true, JSON_THROW_ON_ERROR );
40
41 if ( is_array($payload) && isset($payload['code']) ) {
42 throw new \Exception($payload['code'],$payload['data']['status']);
43 }
Authentication, WordPress, and You 35
44
45 return $payload['token'];
46 }
This example is a little better than the original, but not much. It does allow us to test that everything
is working and for the first time, we can access an endpoint that requires authentication.
If you did not receive any output, it’s time to do a little debugging.
• If your login and password are incorrect, getToken() will throw an exception. That is the easy
one to correct.
• When we build the command to fetch all posts in the status of draft, the first switch we pass
cURL is -s for silent. Change that to -v for verbose to see the conversation happening between
the script and the server. Any line that starts with > is a line the script sent to the server. Any
line that starts with < is a line the server sent back to the script. Look for the line that starts
like this:
1 < HTTP/1.1
That will tell you the status the server sent back to your script. If it is anything but 200, figure out
why.
If it works correctly, you should see a dump of the first post that is in draft status.
Token Management
Now that you have your token, what should you do with it? Honestly, that depends on your
application. Some applications will access the REST API so infrequently that storing the token makes
no sense. Tokens only live for seven days. If your application is only used once a week, it is not worth
the effort to attempt to store it.
If on the other hand, your application is used frequently, yes, store it locally and only renew it when
it fails. JWT tokens can be stored in cookies in browsers or using local storage. If your application
is a CLI script, you can write them to your data store or to the file system.
In theory, JWT tokens can be revoked. However, with the plugin we are using, there currently is no
easy way to do that. The only thing you can do is change the password on the account, but that
won’t invalidate existing tokens, it will just prevent new ones from being issued.
JWT tokens are not as sensitive as login credentials, but they do give access to your system. JWT
tokens should always be passed through a secure (HTTPS) connection. They should never be a
parameter of the URL, but always passed as a header.
Authentication, WordPress, and You 36
Summary
Congratulations, you can now authenticate with the WordPress REST API. Honestly, getting this
right is the hardest part of using the WordPress REST API. You have made it over the hump. Now
let’s look at some of the cooler things we can do with our newfound power.
Modifying Data Using the REST API
Welcome, you are through the looking glass now. Anyone with curl can do the basics with the
WordPress REST API; you have moved beyond that. You can now authenticate with your WordPress
installation and now have access to everything you can do via the web interface. Let’s write some
code.
In this chapter, we will cover the following topics.
From this point on the book, we will not give complete working code listings. When discussing
code, we will examine snippets. You can get the source code for the sample application at Simple
WordPress API Demo. It is highly recommended you fork this repository and then clone your fork
locally. This will allow you the maximum freedom and give you the ability to also submit PRs back
to the project.
You need to make sure both of those requirements are met before progressing. Once they are met,
you can proceed to follow these steps.
1 $ composer install
• username
• password
• the root URL for the REST API on your test WordPress installation, e.g., https://example.com/wp-json.
6. Save config.php.
7. Change directories to the app directory.
8. Execute the following:
1 $ ./console validate
If everything is set up properly and you have entered the credentials properly, this command will
fetch a token and display it. If anything is wrong, it will throw an error, and you will need to debug.
Once it works, you can now move forward in the book.
Manipulating WordPress
If you understood the concepts in chapters two, three, and four, then the sample code will not be
much of a stretch. We have moved from using exec() to using Guzzle to make the calls, but other
than that, it should look familiar. If this is your first time using Guzzle, the format may be confusing,
but you quickly get used to it. It is the most popular PHP HTTP client library and a great tool in
any developer’s toolbox.
The code we are executing is in app/WPREST/Command/MakePostCommand.php. This is a Symfony
Console Command. The SCC is another great tool you need to know if you build a lot of command
line applications in PHP.
Creating a Post
I’m going to skip all the set up and Symfony specific stuff and leave that for you to investigate later
if you like. The heart of the code is in the execute() method.
This command reads a text file data/post.txt, which is just a KEY=VALUE formatted file, and uses
the content for the post we are about to create.
Modifying Data Using the REST API 39
1 $response = $client->request(
2 'POST',
3 $this->getApplication()->config['baseurl'] . '/wp/v2/posts',
4 ['json' => $payload,
5 'headers' => [
6 'Authorization' => 'Bearer ' . $this->token['token'],
7 'Accept' => 'application/json',
8 'Content-type' => 'application/json',
9 ],
10 'debug' => $this->debug
11 ]
12 );
13 $post = json_decode($response->getBody()->getContents());
This is the command which creates the post. If you look in the post.txt file, you will notice we create
it in draft mode.
$payload is the array created from the text file. It contains the bare minimum fields necessary to
create a post. Since we used the array key json, Guzzle will JSON encode the array for us and send
it as the body of the POST.
The second command takes what comes back from the server and JSON decodes it and stores it in
the variable $post. We need that in a minute when we update the post.
Notice the headers we set. We tell the server what content we are sending (Content-type), what
content we expect back (Accept), and we hand it our authentication token in the Authorization:
Bearer header.
That is all it takes to create a post using the WordPress REST API. In a previous chapter, we listed
the parameters the posts endpoint will take and you can add any of those to the post.txt file to
add them to the post.
Uploading an Image
Now that we have a draft post, we need a picture to go with it. This requires us to POST to the media
endpoint.
Modifying Data Using the REST API 40
1 $rawBaseName = 'image.jpg';
2 $file = fopen(
3 $this->getApplication()->config['basepath'] . 'data/' . $rawBaseName ,
4 'r'
5 );
6
7 $response = $client->request(
8 'POST',
9 $this->getApplication()->config['baseurl'] . '/wp/v2/media',
10 [
11 'body' => $file,
12 'debug' => $this->debug,
13 'headers' => [
14 'Accept' => 'application/json',
15 'Cache-Control' => 'no-cache',
16 'Content-type' => 'image/jpeg',
17 'Content-Disposition' => 'form-data; filename="' . $rawBaseName . '"',
18 'Authorization' => 'Bearer ' . $this->token['token']
19 ]
20 ]
21 );
22 $picture = json_decode($response->getBody()->getContents());
This example is only slightly more complex. Guzzle allows us to hand it a stream for the file; it will
read it and send it to the server. The first thing we do is fopen() the image file. No need to read it
or do anything else, just open it for reading.
Then, very similar to the command we used to make the post, we now POST to the /wp/v2/media
endpoint. Instead of json, here we use body. Guzzle will not attempt to JSON decode this since we
use body. It will still send the contents as the body of the POST.
In the headers section, we add a new one Content-Disposition. This is so the server knows how to
handle the file we are sending in. Note that it ends with the name of the file. This is the base name
of the file, no directory structure information, just filename and extension. The server will use this
name when handing it off to WordPress for processing. While the file name does not have to be
the same as it is on your client file system - you can put whatever you want for the filename - best
practices dictate you keep it the same.
The payload you get back is the record that was just created. Among other things, you get the ID
for this image which we will use to attach the image to the post.
It should be noted that when you upload an image, you can only upload the image, you cannot set
any other attributes. If, for instance, you wanted to set a caption, you would need to make a second
call, a PATCH, to the image you just received with the appropriate attributes set in the payload.
Modifying Data Using the REST API 41
1 $payload = [
2 'featured_media' => $picture->id,
3 'status' => 'publish'
4 ];
5
6 $response = $client->request(
7 'PATCH',
8 $post->_links->self[0]->href,
9 ['json' => $payload,
10 'headers' => [
11 'Authorization' => 'Bearer ' . $this->token['token'],
12 'Accept' => 'application/json',
13 'Content-type' => 'application/json',
14 ],
15 'debug' => $this->debug
16 ]
17 );
As you can see, again, it is very similar with the exception that our $payload is only two keys.
You have probably noticed the endpoint we are hitting has changed also. When we created the post,
it contained a collection called _links that we discussed in chapter two. One of the links is self.
This is the URL to the post entity we just created.
Once this call is made, our post is now live. You should be able to point a browser to the URL of
your test WordPress install and see it.
User Management
One of the other big things you can do with the WordPress REST API is manage user accounts,
yours or others. First, remember, that authentication does not equal authorization. Just because
you are logged in (authentication) does not mean you have permission to edit other user accounts
(authorization). For this reason, we are going to stick with updating our own account. The principals
are the same if you are editing other’s accounts.
Modifying Data Using the REST API 42
Our sample code is located in UserManagementCommand.php. It is a very simple script that will get our
information. We will modify the description, and then we will update the system. The first hurdle
we need to overcome is how to get our information. Yes, we are logged in, but we don’t have access
to get_currentuser_id() because this is a REST API call.
Thankfully, our JWT plugin provides us with just enough information to get the job done. If you
examine the payload we store in $this->token, you will notice it is an array, and one of the elements
is user_nicename. This translates into the user’s slug. Slug is a parameter we can use to find a user by.
Also, we are specifying the context of edit. Context can have several different values, view, embed,
and edit. Edit gives us all the information on the user, not just the public-facing information. For
our simple example, it is not strictly necessary since the default context is view and view returns
the description field.
1 $response = $client->request(
2 'GET',
3 $this->getApplication()->config['baseurl'] . '/wp/v2/users/?slug=' . $this->token[\
4 'user_nicename'] . '&context=edit',
5 [
6 'headers' => [
7 'Authorization' => 'Bearer ' . $this->token['token'],
8 'Accept' => 'application/json',
9 'Content-type' => 'application/json',
10 ],
11 'debug' => $this->debug
12 ]
13 );
14 $user = json_decode($response->getBody()->getContents(),true)[0];
It should be noted here we are not exactly requesting a specific user, we are requesting a collection of
users filtered by slug. Since there can only be one user per slug, you will only get a single user back,
but it will be as part of a collection. If you notice, on the last line, the last thing we do is ‘dereference’
the [0] element of the array. This means that $user will be just the user, not the collection.
1 $counter = (int)$user['description'];
2 $user['description'] = (string)(++$counter); // It HAS to be a string
We now have $user['counter'] incremented and ready to send back to the REST API.
Modifying Data Using the REST API 43
1 try {
2 $client->request(
3 'PATCH',
4 $user['_links']['self'][0]['href'],
5 [
6 'json' => ['description' => $user['description']],
7 'headers' => [
8 'Authorization' => 'Bearer ' . $this->token['token'],
9 'Accept' => 'application/json',
10 'Content-type' => 'application/json',
11 ],
12 'debug' => $this->debug
13 ]
14 );
15 } catch (\GuzzleHttp\Exception\GuzzleException $e) {
16 $response = json_decode($e->getResponse()->getBody()->getContents(),true);
17 $output->writeln("An Error has occurred!\n" . print_r($response,true));
18 }
In our example code, we are storing a counter of the number of times we have updated this user in
the description field. This is a very simple example. There is no pressing need to build out a payload
array separately; we can start to streamline our code. As a side note, there is also no pressing need
to cram as many commands together as you can. The example above could be expressed as a single
command joining the call to the API, the fetching of the body contents, and the json_decoding.
While it is possible, it can hamper the readability of the code for some developers. Be cautious in
aggregating commands together. In this case, we simply hard-coded the array on the json key.
Notice that again, we are using the self URL from the _links collection. Remember, we did not
request a specific user but requested a collection filtered by slug. We cannot update the user the
same way. To issue a PATCH, we have to have the URI of the entity we want to update. This is true
for any entity on any REST API.
Also, note that this time the $user variable is expressed as an array and not an object. All code
accessing the element of $user must use array notation.
This PATCH is wrapped in a try/catch statement in case we run into an exception. An example
would be if $user['description] was an integer, not a string. (We are casting it as a string above,
but if you remove the (string), the WordPress REST API will return an error 400.) Guzzle has a
horrible “feature;” if a message from the server is too long, it will truncate it. This means you do not
see the entire message and in most cases, you will not be able to debug the problem. If the above
catch had only caught \Exception, then that is what you would have seen, the truncated message.
The code above shows the proper way to handle an exception thrown by Guzzle. Developers should
catch \GuzzleHttp\Exception\GuzzleException as this is thrown first. Also, it has access to the
Response object. The Response object’s body still contains the complete error message at this point.
Modifying Data Using the REST API 44
You could act on it if you like. In the code above, we are just outputting it so you can see it.
Use the following command to run our new code.
1 $ ./console user
Summary
Hopefully, by now, you are beginning to see patterns in how the API is accessed and updated.
Hopefully, these patterns make sense to you. Once you understand how the endpoints on the
WordPress REST API are called, the rest is just understanding the options and schema needed to
call them. In the next chapter, we are going to briefly examine two sample applications that put the
API to work. Hopefully, by seeing how the API can be used in various scenarios, you can begin to
envision how to use it in your own work.
As an exercise to improve your skills with the API, you are encouraged to add functionality to your
copy of the sample application to allow you to update the image we uploaded with an “alt text”
attribute. If you have been paying attention, then this is not a difficult task. It will, however, force
you to think through the process and modify the code.
Controlling WordPress from Another
Application
We have spent this entire book discussing the details of how to use the WordPress API. We’ve even
created a sample application which will post to a WordPress install. Let’s now bring everything
we have learned together and create a simple application that applies all the principals we have
discussed.
1 $ ./console.sh validate
Controlling WordPress from Another Application 46
• validate
• post
• entities
• delete
validate
We have already covered using the validate command, however, for completeness, we will describe
it here. Validate ensures your URL is correct and that the login credentials work. If all goes well, it
will emit a token. There is no need to copy this token down or save it; it is just there to prove the
application is working.
post
The post command allows us to create a new microblog post. The simplest form of this command is
as follows:
The -t option can take a comma-delimited list of tags. If a given tag does not exist, then it is created
and then applied to the post. If any of the tags contain spaces, wrap the entire list in quotes.
1 $ ./console.sh post -t "example number two, and three" "Another sample post"
Even though we call this a microblogging application, our backend data store (WordPress) does not
limit our character length. It is technically possible - although highly discouraged - to write long-
form posts using this command (up to the character limit for a POST on the web server you are
using to host your WordPress installation). It seems that Apache has a practical limit fo 4GB.
entities
The entities command will list entities from the data store in a nicely formatted table. The basic
format is to list the content of the post and then the tags associated with it.
Controlling WordPress from Another Application 47
1 $ ./console.sh entities
If you pass in a single -v to put the command in VERBOSE mode, the post ID will be listed as well.
This is handy for the next command, delete.
1 $ ./console.sh entities -v
delete
Our sample application allows you to delete an existing post given the post ID.
1 $ ./console.sh delete -p 12
A # Debugging
A If you need to debug the conversation between the app and the server, or if you are just
curious, use -vvv. This puts the application into debug mode. Everything will continue
to operate normally, but the application will emit the complete conversation between the
app and the server. Debug mode works for all commands.
We now have a fully functional microblogging application that uses the WordPress REST API for
storage and retrieval. It would be possible to build a frontend for this application that also uses the
API and never once uses the standard WordPress frontend.
Thin Controllers
In our sample application, the Symfony command serves the purpose of a controller. Like any good
MVC application, we want to make sure the controller is as thin as possible. The logic belongs to the
model.
To that end, let’s examine PostCommand.php, the controller behind posting statuses. The heart of any
Symfony command is the execute() method.
Controlling WordPress from Another Application 48
The first thing to take note of is that we make a call to $this->getToken(), but that method does
not exist. getToken() is used in a variety of places in the application (and in others I have written)
so to keep from copying and pasting it everywhere, getToken() is encapsulated in a trait. In the
case of this application, it could have been included in the BaseCommand class since we have one. It
isn’t because it was written before the BaseCommand. It is neither a bad idea to have this method in
a trait versus the BaseCommand class, nor is it a good idea. Both are valid paradigms for designing
object-oriented systems. In most cases, I test to use traits versus hierarchy, but that is a personal
preference.
Most of the code shown above is set up and teardown code. The central thing to see is creating
the new message object; this is where the magic happens. We create the message object, we set the
properties, and then we call save() to commit it to the data store. If all goes well, then the message
is posted, and the message class is updated with the ID of the post.
If, for some reason, save() failed, an exception would be thrown that is not caught upstream. It
could be caught here if you wanted to handle it more gracefully than the default of displaying it
onscreen.
All of the execute() methods are similar in nature, so this is the only one we will list. The real magic
Controlling WordPress from Another Application 49
Fat Models
In good object-oriented design, the bulk of the work - the business logic - is done by the models.
We call this type of design fat model design. Models, and any objects that help the models, should
contain all the logic to be executed. The controller’s job is basically “traffic cop.”
In PHP, the prevailing design theory is called “composition over inheritance.” This means building
objects by piecing together smaller pieces is preferred over an object inheritance tree. Part of this
bias is a holdover from when inheritance was an expensive CPU process. These days with opcode
caching, inheritance is not nearly as expensive as it used to be. Still, “composition over inheritance” is
a valid way to build systems. It encourages breaking tasks into small pieces like traits and assembling
objects from them.
In our microblogging application, we do both. We have already discussed that getToken() is a trait.
When building out the entity objects, patterns began to emerge. Each entity had a lot of similar
“boilerplate” code. I have abstracted all of this code and placed it in an abstract parent class called
BaseModel. Since most of the logic is in BaseModel, we are going to look at some of it as it applies to
using the WordPress REST API.
The bulk of the work done in BaseModel is done in these three methods. They compose the C,
UUU, and D of C.R.U.D. Most of our controllers only hit the WordPress REST API a single time;
PostCommand is the exception. Because post has to resolve tags passed in, it can hit the API multiple
times with each request. For this reason, and to make unit testing our entity models easier, we pass
the client into the model on __construct(). Each model has a copy of the same client and re-uses
it as needed.
All of our methods also require the authorization token to perform their actions. That is one of
the things we left in the command. Since the creation of an authorization token has nothing to do
specifically with manipulating a status, it does not belong in our entity models. We pass it in as a
parameter on construction.
list
The other main functionality in the base model is the list function. list is different because while
it returns an array of entities, it does not operate on a single entity like the rest of the methods in
the entity class. Therefore, we make list part of the entity base model, but we make it static. list
is not meant to be called on an instantiated object. Since it is a static method, we don’t have access
to $this. We have to pass in everything needed to generate a list of entities.
Controlling WordPress from Another Application 50
The one thing we do know is the class’s entity constant. Each entity defines this constant, and it is
used to build the URL for the endpoints we access. Since it is a class constant, it can be accessed in
both static methods and instantiated objects.
Everything Else
Everything else in BaseModel are helper methods. Sometimes they exist because we were doing
the same thing in two or more places. Other times, they are there to simplify the reading of
other methods. An example of this second type is in Message::resolveTags(). This code is only
called one time, in Message::buildPayload(). Logically, the code could be located there. However,
buildPayload() is much easier to read because we moved this processing into its own method.
Summary
I have specifically left code listings out of most of this chapter. All of the code used in this book is
publicly available. You are encouraged to fork and clone the repository and experiment with it. You
are also encouraged to submit pull requests for any changes you make that you feel make the code
better.
This chapter has been less about the WordPress REST API and more about how the code built to talk
to it was constructed. As I hope you have begun to see, the code necessary to manipulate WordPress
via the API is not difficult and in many cases, easier than working directly in a WordPress plugin
structure.
Conclusion
Making WordPress Work Harder
We set out to understand the basics of the WordPress REST API, and I hope we have accomplished
this. The WordPress REST API is not just a toy to play with. It’s a serious tool that can be used to
make WordPress work harder for you. Not all applications are frontend intensive. WordPress is now
a complete application platform that can be used as a starting point for a wide variety of categories
of applications.
Additional Reading
In an effort of brevity, I have left out a lot of details. Details that are much better served by the
WordPress REST Handbook because they will undoubtedly change over time. If you are doing any
serious work with the WordPress REST API, bookmark this page and pay special attention to the
Reference section. This is where every entity that can be managed by the WordPress REST API is
listed, along with the schema for each. Under each entity, a complete list of arguments are given.
Thank you
Thank you for reading my book. I do hope it was helpful to you. If you have questions about the
materials or comments, I encourage you to drop me an email at [email protected]. I do my best to
respond to every email.
If you find issues with any of the sample code, I urge you to fork the repo and submit a pull request.
Finally, when you build something cool with the WordPress REST API, please drop me an email at
[email protected] and tell me about it. I love to celebrate the successes of others!
Odds and Ends
jv.sh
There are times when you just want to see formatted JavaScript output instead of the JavaScript
blob. For these cases, I write a simple PHP shell script, jv.sh. It is for use on Linux based machines
and assumes you have PHP installed. Yes, there are other options to do this; I didn’t feel it necessary
to install something else when a few lines of PHP would suffice.
To use this, paste this code into a file named jv.sh. Save that file somewhere in your path. Then you
can pipe commands into this, and it will emit formatted JSON.