Update: For more information, you can download our free ebook, The Ultimate Guide to the WordPress REST API.
In this article, I’m going to demonstrate how to write a simple plugin to create and edit posts using the JSON REST API.
While this could be the basis of a cool, front-end editing plugin — with the addition of some fields and CSS, and the implementation of the TinyMCE or similar, of course — the point isn’t to reverse the engineering of the WordPress post editor, but rather to introduce you to processing form data using the REST API and AJAX.
After you understand how it’s done, and how simple it actually is, you can apply what you’ve learned to make your WordPress site more dynamic and app-like.
I’ll also demonstrate how to work with the data in order to update your page dynamically based on the results.
Even if it’s just used to create a front-end post editor, it’s still pretty cool for a few reasons. The first is because it’s fast. That is partially because we only work with the parts of the post we actually need to edit, which is another one of its strengths. If your goal is to keep your clients out of the backend of WordPress, this could be really useful. In fact, one really cool thing you could do is populate the post content from an uploaded document.
Before We Begin
Before we begin, be sure, if you’re not familiar with the WordPress REST API, to read my two-part introduction to it. Pay special attention to the second part, where I discuss how to create posts by making POST requests to the posts endpoint. Today, we’ll be doing something similar, but, instead of using the WordPress HTTP API and PHP, we’ll be working with jQuery’s AJAX methods.
In order for any of this to work, you’ll need to install the WordPress REST API plugin, and the JavaScript client for it. We’ll be using the JavaScript client to make it possible to authorize via the current user’s cookie. You can substitute another authorization method, such as oAuth, if you’d like. Doing so would allow you to create and edit posts on a remote site.
Setting up the Plugin
The plugin we’re creating is going to be fairly simple, and will only need one PHP file and one JavaScript file. In fact that is what’s really cool about all of this — we let the REST API handle most of the heavy lifting for us.
Let’s start by writing the PHP file that will do three key things: 1) enqueue the JavaScript file, 2) localize a dynamically created JavaScript object into the DOM when we are using that file, and 3) create the HTML markup for our form.
To do this we need two functions and two hooks.
Create a new folder in your plugin directory, with one PHP file inside of it. I called the file jp-rest-post-editor.php.
Here’s the php file with empty functions:
<?php /* Plugin Name: JP REST API Post Editor */ add_shortcode( 'JP-POST-EDITOR', 'jp_rest_post_editor_form'); function jp_rest_post_editor_form( ) { } add_action( 'wp_enqueue_scripts', 'jp_rest_api_scripts' ); function jp_rest_api_scripts() { }
In this article we’ll just be working with post title and post content, so in the editor form function, you only need the HTML for a simple form for those two fields, like this:
function jp_rest_post_editor_form( ) { $form = ' <form id="editor"> <input type="text" name="title" id="title" value="Hello there"> <textarea id="content" ></textarea> <input type="submit" value="Submit" id="submit"> </form> <div id="results"> </div> '; return $form; }
Of course, we only want to show this to users who are logged in and can edit posts. So, let’s wrap the variable containing the form in some conditional checks. That way if the current user isn’t logged in, we can give them a link to the WordPress login, and if they are and don’t have permission to edit posts, they still will not see the editor.
Here’s the complete function:
function jp_rest_post_editor_form( ) { $form = ' <form id="editor"> <input type="text" name="title" id="title" value="Hello there"> <textarea id="content" ></textarea> <input type="submit" value="Submit" id="submit"> </form> <div id="results"> </div> '; if ( is_user_logged_in() ) { if ( user_can( get_current_user_id(), 'edit_posts' ) ) { return $form; } else { return __( 'You do not have permissions to edit posts.', 'jp-rest-post-editor' ); } } else { return sprintf( '<a href="%1s" title="Login">%2s</a>', wp_login_url( get_permalink( get_queried_object_id() ) ), __( 'You must be logged in to edit posts, please click here to log in.', 'jp-rest-post-editor') ); } }
One important thing to notice in this form is it does not have a method or an action set in it. That’s so it will not be processed automatically or cause a page reload on submit.
The other thing we need to do is enqueue our JavaScript file. Once that’s done, we’ll also want to localize an array of data into it, which we’ll need in the JavaScript that needs to be generated dynamically. That includes the base URL for the REST API, since that can change with a filter and a nonce for security purposes. We will also want to provide a success and failure message in that array, so that way those strings are translation friendly. Later on, we are going to need to know the current user’s ID, so let’s include that as well.
This is all accomplished via wp_enqueue_script() and wp_localize_script(), which you should be familiar with.
If you want to add custom styles to the editor, this function would be the right place to use wp_enqueue_style() to do so. Here is that function:
function jp_rest_api_scripts() { wp_enqueue_script( 'jp-api-post-editor', plugins_url( 'jp-api-post-editor.js', __FILE__ ), array( 'jquery' ), false, true ); wp_localize_script( 'jp-api-post-editor', 'JP_POST_EDITOR', array( 'root' => esc_url_raw( get_json_url() ), 'nonce' => wp_create_nonce( 'wp_json' ), 'successMessage' => __( 'Post Created Successfully.', 'jp-rest-post-editor' ), 'failureMessage' => __( 'An error occurred.', 'jp-rest-post-editor' ), 'userID' => get_current_user_id(), ) ); }
That’s all the PHP we need. Everything else is going to be handled via JavaScript. Create a new page with the editor shortcode (JP-POST-EDITOR), and go to that page. You should see the post editor form. It’s not going to work yet, of course. We’ll need to write a little JavaScript for that, which I’ll cover in the next section.
Creating Posts
Creating posts from our form is going to require a POST request, which we can make using jQuery’s AJAX method. For the most part, this is very simple, and should be familiar JavaScript. The two things that may be new are creating the JSON array and adding the authorization header. However, both are quite easy. Let’s walk through each of them.
To create the JSON object for our AJAX request, we first create a JavaScript array from the form input and then use JSON.stringify() to convert it into JSON. This is the beginning of the JavaScript file, which shows how to build the JSON array stored in a variable called data:
(function($){ $( '#editor' ).on( 'submit', function(e) { e.preventDefault(); var title = $( '#title' ).val(); var content = $( '#content' ).val(); var JSONObj = { "title" :title, "content_raw" :content, "status" :'publish' }; var data = JSON.stringify(JSONObj); })(jQuery);
We can now pass the variable data to the AJAX request, but first we must set the URL for the request. This step is as simple as appending ‘/posts’ to the root URL for the API, which is accessible via JP_POST_EDITOR.root, so we can do that like this:
var url = JP_POST_EDITOR.root; url += '/posts';
The AJAX request itself is very simple, and looks much like any other AJAX request you would make, except for one important thing: the authorization headers. Thanks to the REST API’s JavaScript client, we simply need to add a header to the request, containing the nonce we set in the JP_POST_EDITOR object. Of course, you could also substitute some other authorization method here, such as oAuth.
Using the nonce to verify cookie authentication just involves setting a request header with the name X-WP-Nonce, which contains the nonce value. We can use the beforeSend argument of the request to send the nonce, and the JavaScript client will handle authentication. Here is what that looks like in the AJAX request:
$.ajax({ type:"POST", url: url, dataType : 'json', data: data, beforeSend : function( xhr ) { xhr.setRequestHeader( 'X-WP-Nonce', JP_POST_EDITOR.nonce ); }, });
Now all we are missing is success and failure functions. We can create alerts using the messages that we localized into the script earlier. For now, let’s output the result of the request as a JSON array so you can see what it looks like.
Here is the complete code for the JavaScript to create a post editor that can create new posts:
(function($){ $( '#editor' ).on( 'submit', function(e) { e.preventDefault(); var title = $( '#title' ).val(); var content = $( '#content' ).val(); var JSONObj = { "title" :title, "content_raw" :content, "status" :'publish' }; var data = JSON.stringify(JSONObj); var url = JP_POST_EDITOR.root; url += '/posts'; $.ajax({ type:"POST", url: url, dataType : 'json', data: data, beforeSend : function( xhr ) { xhr.setRequestHeader( 'X-WP-Nonce', JP_POST_EDITOR.nonce ); }, success: function(response) { alert( JP_POST_EDITOR.successMessage ); $( "#results").append( JSON.stringify( response ) ); }, failure: function( response ) { alert( JP_POST_EDITOR.failureMessage ); } }); }); })(jQuery);
That’s all you need for a basic editor. Right now it just creates posts, but doesn’t edit them.
Give it a try. If you’re logged in and have the API active, create a new post, create an alert telling you the post is created, and then place the returned as a JSON object into the #results container.
Editing Existing Posts
Before we can edit posts, we’ll need to have two things: 1) a list of posts by this author, with all of the post titles and post content, and 2) a new form field to hold the ID of the post we’re editing. We’ll also need to know the ID of the post, but we can get that at the same time.
So, let’s add that hidden field to our form, but first, stick this HTML in before the submit:
<input type="hidden" name="post-id" id="post-id" value="">
Then in the JavaScript function for creating posts, we can add a few lines to get the value of that field and if it has a value, change the URL we are POSTing data to so we can edit the post of that ID, rather than creating a new one:
var postID = $( '#post-id').val(); if ( undefined !== postID ) { url += '/'; url += postID; }
That goes right before the AJAX section of the editor form processor. If that field has a value, then the variable URL in the AJAX function will have the ID of the post we are editing. If not, it will create a new post, just like before.
Now, in order to populate that field, as well as the post title and post content field, we’ll need to add a second form. This one will make a GET request to get all posts by the current user. We can then set the editor form to update based on the selection in that form.
First, in our PHP let’s add the second form to the HTML we output, this is all we need:
<form id="select-post"> <select id="posts" name="posts"> </select> <input type="submit" value="Choose Post To Edit" id="choose-post"> </form>
We’ll use the REST API to populate the options in the #posts select.
To do that we’ll need to make a request for posts by the current user, that way we can use the results to accomplish this goal. I covered making GET requests to the REST API using PHP to get posts in the first part of my introduction to the REST API. If you read it, you will know we just need to add a filter to the URL for the post end point, like this “filter
=1,” where one is the ID of the user to get posts for.When we set up our script, we set the current user ID as part of the JP_POST_EDITOR object. We can use that to form the URL for requesting posts by the current user.
The first thing we need to do is create a function to get posts by the current author and populate that select field. The AJAX request in this function is even simpler than the one we used to update posts, as it does not require authentication. The success function loops through the results and adds them to the post selector form as options for its one field.
function getPostsByUser( defaultID ) { url += '?filter[author]='; url += JP_POST_EDITOR.userID; url += '&filter[posts_per_page]=20'; $.ajax({ type:"GET", url: url, dataType : 'json', success: function(response) { var posts = {}; $.each(response, function(i, val) { $( "#posts" ).append(new Option( val.title, val.ID ) ); }); if ( undefined != defaultID ) { $('[name=posts]').val( defaultID ) } } }); }
You will notice that this function has a parameter for defaultID that if it’s defined will be used to set the default value of the select. For now, you can just ignore that. We will just use this function without the default value and set it to run on document ready, like this:
$( document ).ready( function() { getPostsByUser(); });
Of course, just having a list of posts by the current user isn’t enough. We need to get the post content and title for the selected post and put it into the form for editing. For that we’ll need a second GET request to run on submission of the post selector form. Take a look at it and I’ll go over the important parts below.
$( '#select-post' ).on( 'submit', function(e) { e.preventDefault(); var ID = $( '#posts' ).val(); var postURL = JP_POST_EDITOR.root; postURL += '/posts/'; postURL += ID; $.ajax({ type:"GET", url: postURL, dataType : 'json', success: function(post) { var title = post.title; var content = post.content; var postID = postID; $( '#editor #title').val( title ); $( '#editor #content').val( content ); $( '#select-post #posts').val( postID ); } }); });
The first thing that is happening here is that we are building a new URL to get the post data for the selected post. This will be in the form of <json-url>/posts/<post-id>.
After that we make the actual request, which is the same as the last one I showed you — we take the returned data and set it as the value of the three fields in the editor form.
Now when you refresh the page, you should see all posts by the current user in that selector. When you click its submit button it should do two things: First, the title and content of the post you selected should be visible in the editor now. Second, the hidden field for the post ID we added should also be set.
The function for the editor form is not set for editing existing posts. We’ll need to make a slight modification to it for that. Also we’re going to improve the success function for that request so that it shows the title and content of the post nicely in the #results container, instead of the raw JSON data.
To do this we’ll need one function to update the #results container. It should look like this:
function results( val ) { $( "#results").empty(); $( "#results" ).append( '<div class="post-title">' + val.title + '</div>' ); $( "#results" ).append( '<div class="post-content">' + val.content + '</div>' ); }
This is very simple jQuery, but is a good introduction to updating page content using data from the REST API. You can get more creative with the markup, or add additional fields. It would be especially cool if you incorporated Handlebars or another templating engine here.
Now that that’s in place we can use it in our modified form processing function:
$( '#editor' ).on( 'submit', function(e) { e.preventDefault(); var title = $( '#title' ).val(); var content = $( '#content' ).val(); console.log( content ); var JSONObj = { "title" :title, "content_raw" :content, "status" :'publish' }; var data = JSON.stringify(JSONObj); var postID = $( '#post-id').val(); if ( undefined !== postID ) { url += '/'; url += postID; } $.ajax({ type:"POST", url: url, dataType : 'json', data: data, beforeSend : function( xhr ) { xhr.setRequestHeader( 'X-WP-Nonce', JP_POST_EDITOR.nonce ); }, success: function(response) { alert( JP_POST_EDITOR.successMessage ); getPostsByUser( response.ID ); results( response ); }, failure: function( response ) { alert( JP_POST_EDITOR.failureMessage ); } }); });
The first thing that’s changed is we conditionally add the post ID that’s being edited, just as long as its set in the form to the URL we make the POST request to. That way this form can still serve to create new posts by POSTIng to the endpoint or update posts via posts/<post-id>.
The other thing is that in the success function the new results() function is used to output the post title and content. The other thing is that we rerun the getPostsByUser() function, but set the new or edited post as the default. This way the post editor will automatically be set to edit the post we just created.
You can see the complete JavaScript file here. It’s not completely full featured, but at a little over a hundred lines of code, it’s a pretty good start.
If you can turn it into something useful, please share what you create in the comments!
10 Comments