Update: For more information, you can download our free ebook, The Ultimate Guide to the WordPress REST API
In my article on the future of WordPress, I wrote about how the introduction of a JSON RESTful API to WordPress core will radically expand WordPress’s reach and capabilities.
What is so exciting about this new REST API — which is currently available as a plugin, and slated to be included in WordPress 4.1 — is its ability to use it to not only display content from other WordPress sites, or even other applications entirely, but to save content from other sites, whether they are WordPress sites or not.
In the past, this sort of integration was really only available through XML-RPC. However, the new REST API uses JSON, which is basically a universal connector for data on the internet. What used to be an incredibly complicated process with XML-RPC, can now be simplified by using the much more common JSON standard.
Most programming languages have an easy way to convert their standard data structures into JSON, and convert JSON into their standard data structures. For example, in PHP we have json_encode() and json_decode() to translate from PHP arrays or objects into JSON or the other way around. What this means on a practical level is that WordPress can be the data management tool for an application written in any language and it can easily send and receive data using the JSON standard.
So far the WordPress REST API has predominantly been used for powering single-page, client-side JavaScript apps, either as part of a WordPress theme or plugin, or in a separate front-end client that’s totally separated from the backend. There are some great resources already on how to do that.
I strongly recommend that you read Scott Bolinger’s tutorial on using the REST API to build a simple JavaScript mobile app, which is currently available on the AppPresser site.
The focus of this two-part tutorial will be on how to interact with the REST API using PHP. In this article, I’ll show you how to display posts retrieved via the API.
While the easiest way to test this is by showing posts from the same site, keep in mind that they could be coming from any site, and that the same process would apply to a JSON object sourced from another app or from another JSON API.
Next week, I’ll illustrate how to save and edit posts from a JSON array. Again, I’ll be using WordPress sites, but what makes this whole thing very cool is that the JSON object could be coming from any app or API. Ultimately the goal here is to illustrate just how easy it is to apply what you already know as a WordPress developer to integrate with this widely-used standard. This will enable you to work side-by-side with apps written in any language.
Before We Begin
Before we begin we’ll need to get set up on two sites. On both sites you will need to install the REST API plugin — as it’s not yet part of core.
To make life easier, I’ll be using just two of the default sites provided by VVV, running locally on my computer.
Since I’m using VVV, my two sites will be http://local.wordpress.dev and http://local.wordpress-trunk.dev/ . But you can use any two sites you want.
Since I’m working locally, I can use the basic authentication plugin. That will not be relevant until we talk about saving posts in next week’s article — so I’ll save that discussion for next time.
Just keep in mind that the basic authentication plugin shouldn’t be used on live servers, instead you should use oAuth plugin.
GETing and POSTing
The REST API works using four of the most common HTTP methods — GET, PUT, POST, and DELETE. We will be using GET and POST in this tutorial. We’ll use GET to obtain data via the API and display it, and we’ll use POST to update items. If you have not used a JSON REST API, it may sound complicated or intimidating, but really it’s a matter of creating the right URL strings and sending them in the browser.
In this section we’ll be using simple URL strings to get single posts or all posts. Later in this article I’ll show you how to filter which posts you query for, and how to get items in other post types including pages, attachments, and custom post types.
To illustrate a simple GET request, once you have the REST API installed, go in your browser to http://local.wordpress.dev/wp-json/posts/1 and you will see a JSON object for post ID #1, or a message telling you that you don’t have a post of that ID. If you remove the ID from the end of the URL you will see all posts.
Congrats you just made your first GET request to the JSON API!
Next week I’ll illustrate how to use POST actions to save posts.
(By the way, if GET and POST sound familiar from working with forms, that’s no coincidence, it’s the same thing. The amount of new things you are learning here is not very long.)
The WordPress HTTP API
The REST API isn’t the only impressive API that WordPress offers. One of the lesser used — compared to say the post API, hook API, or options API — is the HTTP API, which simplifies making HTTP requests. The HTTP request makes WORDS easy. With a few lines of code, we can use the HTTP API to turn a URL string into a JSON object.
The HTTP API gives us several useful functions to make HTTP requests via PHP. The simplest example would be:
$json = wp_remote_get( 'http://local.wordpress.dev/wp-json/posts/1' );
If you were to output $json now, you would see that the return isn’t actually the JSON object we want. It’s an array, with an index called body, which has the JSON object. For the most part, that’s actually all we need. We can get the body directly with wp_remote_retrieve_body(). In general, we don’t want to use it directly though, as it will produce an error if the URL string passed isn’t valid.
So let’s put together the simplest PHP API client that will take a URL string and output a PHP array of post objects. Our mini-client is just one function. Take a look at it, I’ll explain it below:
$response = wp_remote_get( $url ); function slug_get_json( $url ) { //GET the remote site $response = wp_remote_get( $url ); //Check for error if ( is_wp_error( $response ) ) { return sprintf( 'The URL %1s could not be retrieved.', $url ); } //get just the body $data = wp_remote_retrieve_body( $response ); //return if not an error if ( ! is_wp_error( $data ) ) { //decode and return return json_decode( $data ); } }
This function starts from a URL string, and makes a GET request to load it into $request. Then we make sure that the request works by testing that $request is not an object of the WP_Error class, which is what it will be if the HTTP request failed. If so, we return an error message. If not, we pass $response to wp_remote_retrieve_body() to get the JSON object into $data. Again, we must check that we didn’t get an error, if we didn’t, we use json_decode() to return an array of post objects.
Go ahead and feed it a URL string like http://local.wordpress.dev/wp-json/posts/1 and run the results through print_r to see what you got. Where this gets really cool is if you pass it a URL string from another site. In my example set up in local.wordpress.dev, I can pass in the string http://local.wordpress-trunk.dev/wp-json/posts and see posts from the second site.
Making Queries
Just getting a specific post or all posts via the API is only so useful. What if we wanted specific posts based on various conditions, just like we’re use to doing with WP_Query. Luckily we have a subset of WP_Query parameters available to us with the REST API. Opposed to WP_Query, we can use it to query other sites.
By adding filter parameters to the URL string, we can query for post statuses other than the default ‘publish’ status, or we can change how many posts we return or how they ordered. We can even run a search by post title. For full documentation of these parameters, see the REST API documentation.
For example, if we wanted to retrieve 8 posts in reverse alphabetical order by post title, we could use this URL string:
http://local.wordpress.dev/wp-json/posts?filter[posts_per_page]=8&filter[order]=DESC
Typing out that URL is a pain, and I didn’t actually do it, since WordPress can do it for me. One way to do it is to use the function add_query_arg(). If we pass it an array of arguments as the first parameter, and use our base url as the second parameter, it will give us the right URL string.
For example, here is how I created it:
$args = array( 'filter[orderby]' => 'title', 'filter[posts_per_page]' => 8, 'filter[order]' => 'DESC' ); $url = add_query_arg( $args, json_url( 'posts' ) );
You can also choose to query for one or more post types using the posts[] parameter. For example, to query for a post in custom post types called “books” and “authors,” you would use this string:
http://local.wordpress.dev/wp-json/posts?type[]=book&type[]=author
Even with add_query_arg(), creating these strings can be complicated and tedious. Instead of continuing this way, let’s build a function to build these URL strings for us.
The function accepts a post type, or an array of post types, as well as an array of filters in the form of ‘filter_name’ => ‘value’. Here’s the function:
function slug_api_posts_url_string( $post_types = 'post', $filters = false, $rooturl = 'home_url', $base = '/wp-json/posts?' ) { if ( is_callable( $rooturl ) ) { $url = call_user_func( $rooturl ); $url = $url.$base; } else { $url = $rooturl.$base; } if ( is_string( $post_types ) ) { $post_types = array( $post_types); } foreach ( $post_types as $type ) { $url = add_query_arg( "type[]", $type, $url ); } if ( $filters ) { foreach( $filters as $filter => $value ) { $args[ "filter[{$filter}]" ] = $value; } $url = add_query_arg( $args, $url ); } return $url; }
This function makes it really easy to build the type of URL strings we need for the REST API. For example, to get 50 posts in 4 different post types, ordered by their modified date, skipping the first 10 posts with this new function we can create the URL like this:
$filters = array( 'posts_per_page' => 50, 'orderby' => 'modified_gmt', 'offset' => '10' ); $post_types = array( 'post', 'page', 'book', 'author' );
If you look closely, you will notice that the third parameter, $rooturl, can accept either a URL or a function name. This allows you to pass a function — such as the default, home_url(), or a full URL — to use as the base for building the URL string. The default value will query the current site’s API. Alternatively, we can pass in another site’s URL, or a function representing it to get posts from that site’s API. For example, to get pages from my other local development site I would enter:
$url = slug_api_posts_url_string( 'page', false, 'http://local.wordpress-trunk.dev/' );
Outputting the Posts with PHP
Getting these JSON objects is pretty awesome, but we need to be able to output the posts using the two functions I showed early. Together they give us an array of objects. Each object has a variety of property for all of the fields that make up a WordPress post.
This means that working with these objects is very much like working with the post objects that you’re probably used to working with when you access the global $post object, or when you get an object with get_post(). Here is a simple loop that iterates through all posts retrieved by the REST API, and outputs them in standard HTML5 markup, simplified from the content-single.php from _s:
$url = slug_api_posts_url_string( 'posts', array( 'posts_per_page' => 10 ) ); $posts = slug_get_json( $url ); if ( is_array( $posts ) && count( $posts ) > 0 ) { foreach( $posts as $post ) { ?>
<!--?php } }
I’m aware that what I’ve shown you above is an efficient way of doing what you can already do with a WordPress theme. I’m showing it to you for two very important reasons.
The first reason is because this exercise will familiarize you with the data that the REST API returns, and in turn shows just how easy it is to use. The second, and more important reason is that with almost exactly the same code, with a different URL, you can show posts from a completely different site, and that’s not something you can do already with WordPress.
Just the Beginning
You may have noticed that in this article I’ve only talked about working with post types, but you should keep in mind that the REST API supports GET request for all content types, and has varying support for the other types of requests on other content types, as illustrated in this chart:
21 Comments