In this article you will learn how to create a front-end WordPress post submission form using the WP-API. We’ll focus specifically on why this is better than the “old way” of using admin-ajax.
Why would you want to do this? Because it’s faster, easier, and more secure. Let’s take a look at how it’s done.
Using admin-ajax as the endpoint to process front-end AJAX requests has become a standard because it works, and it’s seen as “the WordPress way.” But it’s not particularly performant. In addition, it requires setting up a system for validation, sanitization and nonce checking for each request.
Of course, many developers, myself included, create systems for abstracting their use of admin-ajax, or a create custom APIs for front-end AJAX. That’s a smart thing to do, but it doesn’t contribute to code maintainability since everyone has their own way of doing it and there is no real standard.
The REST API can change that. It defines “The WordPress Way” for creating custom APIs, which can be used for responding to front-end AJAX requests. The REST API provides fantastic architecture for sanitization, validation, and authentication of requests.
In this article, I’m going to show you how to create a front-end form that will let users with the contributor role submit posts. A great use case for this sort of system is to allow contributors to submit drafts or story pitches from the front-end.
The example I am showing relies on WordPress’ existing capabilities system and trusts it to prevent the unauthorized creation of published posts — something that is a big risk, when wrapping a traditional AJAX request around wp_insert_post().
This practical example is also designed to show you, in general, how you can use the REST API to replace admin-ajax in the front-end. Because of the example I have chosen, we can use a default post endpoint “wp-json/wp/v2/posts” instead of creating a custom endpoint. That’s awesome, and means we’ll be writing almost no server-side code.
In some cases you will need to add custom endpoints to the REST API for your AJAX. When that is needed, I recommend reading through the docs on extending the API.
Also, please keep in mind that this article assumes you are using version 2 of the API. It is available as a plugin, and hopefully will be merged into WordPress core soon.
If you want to see the complete source for the examples in this article, you can. I have packaged them into a small plugin:
Download Post Submitter Plugin
What If I Don’t Want To Return JSON?
In the example I am showing you in this article, JSON is a perfectly acceptable format to get the data back in. In many cases, when you are looking to replace admin-ajax with the REST API, you may prefer to get plain text, HTML, or even images back from the API.
While the REST API is designed to return JSON, it is also highly extensible. The “rest_pre_serve_request” filter provides an entry point to hijack a request and respond with any data you want. That could be a previously cached response, text, XML, an image — whatever. If that filter is set to anything besides null, then its value will be served, instead of continuing with the rest (pun intended) of the REST API’s process.
Making It Work
Honestly, there is nothing particularly exciting about what I’m about to show you. It’s very standard stuff. That’s exactly the point I’m trying to get across. The fact that I have to follow this section on how to make it work, with a breakdown of what I didn’t have to do, speaks to how awesome this new REST API really is.
The Form
To make this work, I created a form with three fields: title, excerpt and content. I left content as a non-required field as that allows excerpt to be used to create “pitches” for posts. You can adapt the form to fit your needs.
I wrote a simple function, hooked to the_content to create my form markup when a page with the slug “submit-post” was being outputted. You could write your form directly into a template file in your theme or child theme if you wanted.
In addition, I chose to only output the form for logged in users, who had the capability to edit posts — IE those with the contributor role or higher. This isn’t 100% necessary as the form wouldn’t work for logged out users, or subscribers. But, there is no need to show a non-functional form. Instead I created a link to wp-login for non-logged in users.
Here is the hook and function that creates the markup:
add_filter( 'the_content', function( $content ) { if ( is_page( 'submit-post' ) ) { //only show to logged in users who can edit posts if ( is_user_logged_in() && current_user_can( 'edit_posts' ) ) { ob_start();?> <form id="post-submission-form"> <div> <label for="post-submission-title"> <?php _e( 'Title', 'your-text-domain' ); ?> </label> <input type="text" name="post-submission-title" id="post-submission-title" required aria-required="true"> </div> <div> <label for="post-submission-excerpt"> <?php _e( 'Excerpt', 'your-text-domain' ); ?> </label> <textarea rows="2" cols="20" name="post-submission-excerpt" id="post-submission-excerpt" required aria-required="true"></textarea> </div> <div> <label for="post-submission-content"> <?php _e( 'Content', 'your-text-domain' ); ?> </label> <textarea rows="10" cols="20" name="post-submission-content" id="post-submission-content"></textarea> </div> <input type="submit" value="<?php esc_attr_e( 'Submit', 'your-text-domain'); ?>"> </form> <?php $content .= ob_get_clean(); }else{ $content .= sprintf( '<a href="%1s">%2s</a>', esc_url( wp_login_url() ), __( 'Click Here To Login', 'your-text-domain' ) ); } } return $content; });
Feel free to modify this form to fit your needs. Just make sure to keep the IDs of the form fields in sync with the JavaScript we’ll be creating shortly.
Loading The JavaScript
We’re going to need a little bit of JavaScript to make this form work. To do so, we’ll want to load a file, using wp_enqueue_script(), like we would any other script.
In addition, we’ll want to make a few pieces of information available to the JavaScript. We can do this with wp_localize_script(). What we will need to make available is the root URL of the REST API, and a nonce with the action “wp_rest”. I also added two strings to the localized object — one for a success message and one for a failure message. Doing this server side makes the strings translatable.
The most important part of what we are doing in this step, is making a nonce. The REST API will use the regular WordPress auth cookie to authenticate the user, but only if that nonce is present. If you’re not familiar with nonces, then that’s OK, it’s a cool trick to prevent cross-scripting attacks and otherwise ensure the data submitted to a server is coming from who the nonce was created for, not a malicious user, bot or other evildoer.
Here is how I set up my JavaScript:
add_action( 'wp_enqueue_scripts', function() { //load script wp_enqueue_script( 'my-post-submitter', plugin_dir_url( __FILE__ ) . 'post-submitter.js', array( 'jquery' ) ); //localize data for script wp_localize_script( 'my-post-submitter', 'POST_SUBMITTER', array( 'root' => esc_url_raw( rest_url() ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'success' => __( 'Thanks for your submission!', 'your-text-domain' ), 'failure' => __( 'Your submission could not be processed.', 'your-text-domain' ), 'current_user_id' => get_current_user_id() ) ); });
The JavaScript
Ok, so now we’re ready for the main event. I’m sorry, but it’s really not that exciting. If you’re familiar with how AJAX works, this should be very familiar looking.
All we are doing is getting the data from the form, POSTing it via AJAX to the posts endpoint of the API — “/wp-json/wp/v2/posts”. If the server returns a success code, we alert the success message setup in the last step, if not, we alert the failure message.
The most important thing to notice in this code is the beforeSend function. This is where we add the nonce to the header of the request. The nonce must be in a header called “X-WP-Nonce” for the REST API to pick it up. To do this we use xhr.setRequestHeader.
Here is the full JavaScript we need:
jQuery( document ).ready( function ( $ ) { $( '#post-submission-form' ).on( 'submit', function(e) { e.preventDefault(); var title = $( '#post-submission-title' ).val(); var excerpt = $( '#post-submission-excerpt' ).val(); var content = $( '#post-submission-content' ).val(); var status = 'draft'; var data = { title: title, excerpt: excerpt, content: content }; $.ajax({ method: "POST", url: POST_SUBMITTER.root + 'wp/v2/posts', data: data, beforeSend: function ( xhr ) { xhr.setRequestHeader( 'X-WP-Nonce', POST_SUBMITTER.nonce ); }, success : function( response ) { console.log( response ); alert( POST_SUBMITTER.success ); }, fail : function( response ) { console.log( response ); alert( POST_SUBMITTER.failure ); } }); }); } );
What We Didn’t Do
What I have shown so far is very standard stuff and that’s what’s exciting about it. What is actually exciting is what we didn’t have to do.
If I was creating the same functionality using admin-ajax, I would have needed to verify the nonce, ensured the current user had the right capabilities, validated and sanitized the data for the post. In this example, we are able to offload all of that to the REST API. Another thing we didn’t do here was set the post author. The REST API will handle that for us.
I can’t overstate the importance of this. We use WordPress because it handles security for us. The more we follow standards the less we have to do and the more we can rely on code that has been reviewed and used many times over.
It’s always important to remember that when writing JavaScript, any code you write can be edited in the browser. I didn’t include a key in post data object for post status, but it would be trivial to add that in the browser. But here is the thing, if someone did add it and set it to “publish” the only way that would work is if they were logged in as a user who could publish a post.
If I was writing this same functionality using admin-ajax, I would have to validate post status, and post author, and ensure that none of the fields I was using contained malicious data. I’d also have to trust myself not to screw any of that up. Not that I ever make mistakes…
All That And It’s Faster
The benefits of using the REST API as a replacement for admin-ajax are not limited to increased security and easier development. It’s also way more efficient than using admin-ajax when the admin isn’t actually needed. Pretty awesome, right?
I encourage you to take what you have learned and apply it to other cool uses of AJAX to make your sites more dynamic. You can use it to lazy load posts, or images. You can write custom endpoints to pretty much anything. Like the all of WordPress, the REST API is a highly extensible tool, limited only by your creativity, and your willingness to experiment. I hope you use it to create awesome things.