5 tips for using AJAX in WordPress

AJAX is used in many WordPress themes and plugins. However, not all of those themes and plugins implement AJAX properly. This article reveals 5 best practices in developing AJAX for WordPress.

Warning: This is a long and information-packed article. Be sure to bookmark this post so you can always go back for reference! The links below makes it easier to navigate this article.

 

Table of Content

Bad ways to implement AJAX

I have read the code of many plugins that implement AJAX. While some of them do it the right way, most of them don’t. The common pattern in those plugins is: a PHP file to handle the AJAX request, and another PHP file that spits out javascript code to make the AJAX request.

The PHP file that handles the AJAX almost always begin with this line:

1
2
require_once( "../../../../wp-config.php" );
// or require_once( "../../../../wp-load.php" );

What’s wrong with this is that sometimes users set up their folder structure differently, making the relative path declared in the PHP file above invalid. Another downside to this, is if you’re using Object Oriented approach to develop your plugin (wrapping your plugin functions in a class), you won’t have direct access to the necessary properties and private functions of the class.

Along with the PHP file above, another PHP file is necessary to output the javascript that makes the AJAX request. This has to be a PHP file instead of a normal JavaScript file, because (again) it has to include wp-config.php (or wp-load.php), so that it can get the absolute URL to the PHP file that handles the request, or load some additional data from the database. In order to load this “fake” javascript file, the whole WordPress framework needs to be loaded again.

Sorry for keeping you off the good part of the article so long. But avoiding doing things the wrong way is as important as knowing how to do them right. Without further ado, here are 5 crucial tips for implementing AJAX in WordPress properly.

1. Use wp_localize_script() to declare javascript global variables

Although wp_localize_script() is created for localization, it also has another great use. You can declare javascript variables with namespaces to use with your script. Here’s the syntax:

1
wp_localize_script( $handle, $namespace, $variable_array );

Here’s how you should declare the URL to the file that handles AJAX (in this example, I use admin-ajax.php, which is discussed in tip #2):

1
2
3
4
5
// embed the javascript file that makes the AJAX request
wp_enqueue_script( 'my-ajax-request', plugin_dir_url( __FILE__ ) . 'js/ajax.js', array( 'jquery' ) );
// declare the URL to the file that handles the AJAX request (wp-admin/admin-ajax.php)
wp_localize_script( 'my-ajax-request', 'MyAjax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' ) ) );

This way, you won’t have to use PHP to print out JavaScript code, which is both ugly and non-cacheable. If you take a look at the generated HTML in the <head> element, you’ll find this:

1
2
3
4
5
6
7
8
<script type="text/javascript" src="http://example.com/wordpress/wp-content/plugins/myajax/js/ajax.js"></script>
<script type="text/javascript">
/* <![CDATA[ */
var MyAjax = {
};
/* ]]> */
</script>

Now, in your ajax.js file, you can use MyAjax.ajaxurl without having to resort to PHP and including wp-load.php. Please refer to tip #2 below for more about setting up the javascript code that sends the request, and how to handle the request properly.

2. Use admin-ajax.php to handle AJAX requests

AJAX requests should be directed to wp-admin/admin-ajax.php. I know the “admin” part of the file name is a bit misleading, but all requests in the front-end (the viewing side) as well as the admin panel can be processed in admin-ajax.php.

There’s a required parameter for a request sent to admin-ajax : it’s called action. This parameter is necessary because when admin-ajax process the request, it will fire one of these hooks, depending on whether the current viewer is logged in or not:

1
2
3
4
5
// this hook is fired if the current viewer is not logged in
do_action( 'wp_ajax_nopriv_' . $_REQUEST['action'] );
// if logged in:
do_action( 'wp_ajax_' . $_POST['action'] );

The JavaScript code that submits the AJAX request should look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jQuery.post(
    // see tip #1 for how we declare global javascript variables
    MyAjax.ajaxurl,
    {
        // here we declare the parameters to send along with the request
        // this means the following action hooks will be fired:
        // wp_ajax_nopriv_myajax-submit and wp_ajax_myajax-submit
        action : 'myajax-submit',
        // other parameters can be added along with "action"
        postID : MyAjax.postID
    },
    function( response ) {
        alert( response );
    }
);

Now all we have to do is hook our existing plugin or theme file up with those provided ajax actions without having to create a separate file to process the request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// if both logged in and not logged in users can send this AJAX request,
// add both of these actions, otherwise add only the appropriate one
add_action( 'wp_ajax_nopriv_myajax-submit', 'myajax_submit' );
add_action( 'wp_ajax_myajax-submit', 'myajax_submit' );
function myajax_submit() {
    // get the submitted parameters
    $postID = $_POST['postID'];
    // generate the response
    $response = json_encode( array( 'success' => true ) );
    // response output
    header( "Content-Type: application/json" );
    echo $response;
    // IMPORTANT: don't forget to "exit"
    exit;
}

3. Use nonces and check for permission

It can’t be stressed enough that you need to check for permission for every action a user takes. Developers often neglect this, and the results are disastrous, because without layers that checks for sufficient permissions, a malicious user could escalate his privilege and do all sorts of naughty things with your website that you have spent so much effort to build. There are two layers of security that you need to apply to all AJAX request most of the time.

Nonces

Nonces are Numbers that are generated and used once. Nonces are used to make sure the action is initiated by the authorized user from a certain location. Here’s an interesting analogy by Jon Sykes, which makes everything easier to understand:

When you arrive at a deli counter you’re asked to take a ticket. Once the counter reaches your number you had the ticket back to the server (who throws it away) and they serve you. They then move onto serving the next person in the who’s ticket matches the number of the digital screen.

If someone comes along and tries to jump the line (queue) they can’t unless they have a ticket.

If they manage to get hold of someones old ticket, they can’t use it, as it’s already been used and the digital counter has moved on.

To further extend the analogy, you then make every customer sign for the ticket when they take one, and you then check not only that there ticket matches but also make them sign again to check that their first signature matches the one they have before they get served.

Taking it to a silly level, you’d only allow users to get a ticket when they walked in the front door of the store (that they have to sign for). This then prevents someone climbing in a window and trying to forge your signature to get served. Because they didn’t come in the front door, they don’t have any access to the tickets, so they have no way of jumping ahead of you and ordering the last of the Bologna.

Mark Jaquith and Vladimir Prelovac also covered Nonces and how to use them generally. Make sure you read those articles because you’ll definitely need them for almost all kinds of WP development (not only AJAX related).

However, it should be stressed that nonces are useful only in situations where the AJAX request has something to do with data or content manipulation. If the request is only for retrieving comment count, or post content for example, nonces are not required. AJAX requests to post / edit / delete contents definitely need nonce checking.

OK, enough theory. Here’s how to implement nonces in your AJAX request.

First, you need to generate a nonce, and use wp_localize_script() to include it as a javascript variable:

1
2
3
4
5
6
7
8
9
10
11
12
// embed the javascript file that makes the AJAX request
wp_enqueue_script( 'my-ajax-request', plugin_dir_url( __FILE__ ) . 'js/ajax.js', array( 'jquery' ) );
wp_localize_script( 'my-ajax-request', 'MyAjax', array(
    // URL to wp-admin/admin-ajax.php to process the request
    'ajaxurl'          => admin_url( 'admin-ajax.php' ),
    // generate a nonce with a unique ID "myajax-post-comment-nonce"
    // so that you can check it later when an AJAX request is sent
    'postCommentNonce' => wp_create_nonce( 'myajax-post-comment-nonce' ),
    )
);

Now your JavaScript can use the global variable MyAjax.postCommentNonce to get the nonce. You have to send the nonce along with your request so that the AJAX handler can check it:

1
2
3
4
5
6
7
8
9
10
11
12
13
jQuery.post(
    MyAjax.ajaxurl,
    {
        action : 'myajax-submit',
        postID : MyAjax.postID,
        // send the nonce along with the request
        postCommentNonce : MyAjax.postCommentNonce
    },
    function( response ) {
        alert( response );
    }
);

Here’s essentially the same code as in tip #2, except that I’ve added nonce validation to make sure the request is valid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// if both logged in and not logged in users can send this AJAX request,
// add both of these actions, otherwise add only the appropriate one
add_action( 'wp_ajax_nopriv_myajax-submit', 'myajax_submit' );
add_action( 'wp_ajax_myajax-submit', 'myajax_submit' );
function myajax_submit() {
    $nonce = $_POST['postCommentNonce'];
    // check to see if the submitted nonce matches with the
    // generated nonce we created earlier
    if ( ! wp_verify_nonce( $nonce, 'myajax-post-comment-nonce' ) )
        die ( 'Busted!')
    // get the submitted parameters
    $postID = $_POST['postID'];
    // generate the response
    $response = json_encode( array( 'success' => true ) );
    // response output
    header( "Content-Type: application/json" );
    echo $response;
    // IMPORTANT: don't forget to "exit"
    exit;
}

There, all done. Now here’s an important note. If you want to allow the user to initiate the AJAX request multiple times (like posting multiple comments on the same page) , you need to regenerate a new nonce, include it in your response, and then reassign that new nonce to MyAjax.postCommentNonce. Otherwise, the same nonce would be used over and over again, and the subsequent AJAX requests will fail.

Check for permissions

Lots of plugins neglect to check for sufficient permission for AJAX requests. Always use current_user_can() to make sure the request is made by an authorized user. Here’s the final bulletproofed version of the request handling code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// if both logged in and not logged in users can send this AJAX request,
// add both of these actions, otherwise add only the appropriate one
add_action( 'wp_ajax_nopriv_myajax-submit', 'myajax_submit' );
add_action( 'wp_ajax_myajax-submit', 'myajax_submit' );
function myajax_submit() {
    $nonce = $_POST['postCommentNonce'];
    // check to see if the submitted nonce matches with the
    // generated nonce we created earlier
    if ( ! wp_verify_nonce( $nonce, 'myajax-post-comment-nonce' ) )
        die ( 'Busted!')
    // ignore the request if the current user doesn't have
    // sufficient permissions
    if ( current_user_can( 'edit_posts' ) ) {
        // get the submitted parameters
        $postID = $_POST['postID'];
        // generate the response
        $response = json_encode( array( 'success' => true ) );
        // response output
        header( "Content-Type: application/json" );
        echo $response;
    }
    // IMPORTANT: don't forget to "exit"
    exit;
}

4. Use the built-in jQuery Form plugin to submit forms

Most of the time you use AJAX to avoid page reload when submitting forms. Not everyone knows that WordPress also provide a jQuery plugin to deal with AJAX form submission. To use this jQuery plugin, include it by using “jquery-form” as the handle:

1
wp_enqueue_script( 'json-form' );

Then, submitting form using AJAX is a breeze:

1
2
3
4
5
6
7
8
9
10
11
12
jQuery('#myForm1').ajaxForm({
    data: {
        // additional data to be included along with the form fields
    },
    dataType: 'json',
    beforeSubmit: function(formData, jqForm, options) {
        // optionally process data before submitting the form via AJAX
    },
    success : function(responseText, statusText, xhr, $form) {
        // code that's executed when the request is processed successfully
    }
});

There are a lot more options for you to configure. Check out jQuery Form plugin documentation for more details.

5. Be careful with jQuery default JSON parsing

At the moment of this writing, the latest stable version of WordPress is 2.9.2 . In this version of WordPress, the default jQuery version is 1.3.2 .

In jQuery 1.3.2 or earlier, jQuery uses eval to convert a JSON string into an object. While this is fast, it’s not safe:

The eval function is very fast. However, it can compile and execute any JavaScript program, so there can be security issues. The use of eval is indicated when the source is trusted and competent. It is much safer to use a JSON parser. In web applications over XMLHttpRequest, communication is permitted only to the same origin that provide that page, so it is trusted. But it might not be competent. If the server is not rigorous in its JSON encoding, or if it does not scrupulously validate all of its inputs, then it could deliver invalid JSON text that could be carrying dangerous script. The eval function would execute the script, unleashing its malice.

from json.org

jQuery 1.3.3 and later will automatically check if there’s already a JSON parser, and use that parser instead of eval(). WordPress also provides the javascript JSON parser in wp-includes/js/json2.js. Including it is simple:

1
wp_enqueue_script( 'json2' );

Then, to make sure your script parses JSON the safe way, there are two ways:

The first way is to include your own version of jQuery (equal or later than 1.3.3)

1
2
3
wp_enqueue_script( 'json2' );
// don't forget to specify dependency
wp_enqueue_script( 'myjQuery', plugin_dir_url( __FILE__ ) . 'js/jquery.1.4.2.js', array( 'json2' ) );

The second way is to use WordPress supplied jQuery, but make sure that you don’t specify “json” as the response type, and use the included JSON parser to do the dirty job. You need to set the dependency first:

1
2
3
// embed the javascript file that makes the AJAX request
// don't forget to specify 'json2' as the dependency
wp_enqueue_script( 'my-ajax-request', plugin_dir_url( __FILE__ ) . 'js/ajax.js', array( 'jquery', 'json2' ) );

Then use JSON parser to parse the response string:

1
2
3
4
5
6
7
jQuery.post( MyAjax.ajaxurl, { action : 'myajax-submit' },
    function( jsonString ) {
        // use JSON parser on the response string instead of
        // specifying the response type as json
        var object = JSON.parse(jsonString);
    }
);

Conclusion

AJAX makes interaction with users more smooth and responsive, and is highly recommended for consideration when you develop your own plugins or themes. However, it’s vital that AJAX is implemented correctly and securely, otherwise the consequence could be disastrous. This article covers 5 most essential tips that all serious WordPress developers should employ to make sure their AJAX plugins or themes function well and safe.

Do you have other AJAX tips or tricks that you want to share? Drop in a comment below. Your input is always appreciated!