Ajax in WordPress

The purpose of this article is to demonstrate how to use AJAX when creating themes and plugins.

Useful links:

AJAX in the WordPress Admin Panel

Since AJAX was integrated into the WP admin panel, using AJAX functionality in plugins has become very convenient. Here's a small example. Everything is done in one file (plugin file or functions.php file in the theme).

#1. Adding JavaScript

First, add the JavaScript code to the admin page that will send the AJAX request.

<?php
//add_action( 'admin_print_scripts', 'my_action_javascript' ); // this type of connection will not always work
add_action( 'admin_print_footer_scripts', 'my_action_javascript', 99 );
function my_action_javascript() {
	?>
	<script>
	jQuery(document).ready( function( $ ){
		var data = {
			action: 'my_action',
			whatever: 1234
		};

		// since version 2.8, 'ajaxurl' is always defined in the admin panel
		jQuery.post( ajaxurl, data, function( response ){
			alert( 'Received from the server: ' + response );
		} );
	} );
	</script>
	<?php
}
?>

Since version 2.8, the JavaScript variable ajaxurl is globally defined on all admin pages. Use it in the JS code as a reference to the AJAX request handler file. Usually, this is the file /wp-admin/admin-ajax.php. This variable is not defined in the theme (template). To use it in the frontend, you need to define it yourself. See below for how to do it.

#2. Creating a PHP Function

Now, let's create a PHP function to handle the AJAX request. Add the following code to functions.php (or in the plugin):

add_action( 'wp_ajax_my_action', 'my_action_callback' );
function my_action_callback(){
	$whatever = intval( $_POST['whatever'] );

	$whatever += 10;
	echo $whatever;

	// exiting is necessary so that there is nothing unnecessary in the response, only what the function returns
	wp_die();
}

Here, we hook into the wp_ajax_my_action action - it looks like this: wp_ajax_(action), where the value of the variable passed in the first code is inserted instead of (action): action = my_action.

That's it.

The above example is sufficient to start using AJAX in the WordPress admin panel.

Whenever possible, use wp_die() instead of die() or exit(), in the AJAX request handling function. This will provide better integration with WordPress and, in case of errors in the code, you will receive information about them.

AJAX on the Frontend (in the Theme)

The first thing to ensure is whether the jQuery library is installed on the site.

In the frontend (public-facing part of the site), another hook needs to be used to handle AJAX requests: wp_ajax_nopriv_(action). Unlike wp_ajax_(action), this hook works for unauthorized users.

That is, to create a request handler for all users: authorized and unauthorized, the PHP function needs to be attached to both hooks:

add_action( 'wp_ajax_(action)', 'my_action_callback' );
add_action( 'wp_ajax_nopriv_(action)', 'my_action_callback' );

wp_ajax_nopriv_(action) can be omitted if you don't want the AJAX request to be handled for unauthorized users.

Variable ajaxurl

Remember that the variable ajaxurl exists only in the admin panel and is not available in the frontend, so it needs to be defined (created). However, we will name it differently - myajax.url, which is more convenient for the frontend, as it allows additional data related to the AJAX request to be added to the myajax object.

The correct way to create such a variable is to use the function wp_localize_script().

// Add localization at the very end of the scripts being output, so that the script
// 'twentyfifteen-script' to which we are attaching is definitely added to the queue for output.
// Note: you can insert the code anywhere in the theme's functions.php
add_action( 'wp_enqueue_scripts', 'myajax_data', 99 );
function myajax_data(){

	// The first parameter 'twentyfifteen-script' means that the code will be attached to the script with the ID 'twentyfifteen-script'
	// 'twentyfifteen-script' must be added to the output queue, otherwise WP won't understand where to insert the localization code
	// Note: this code is usually added in functions.php where scripts are included, after the specified script
	wp_localize_script( 'twentyfifteen-script', 'myajax',
		array(
			'url' => admin_url('admin-ajax.php')
		)
	);

}

As a result, in the head section of the site, just before the 'twentyfifteen-script' script, we will get:

<script type='text/javascript'>
/* <![CDATA[ */
var myajax = {"url":"http://example.com/wp-admin/admin-ajax.php"};
/* ]]> */
</script>
<script type='text/javascript' src='http://example.com/wp-content/themes/twentyfifteen/js/functions.js?ver=20150330'></script>

Now, everything is the same as for the admin section, except instead of ajaxurl, we specify myajax.url and need to attach the handler function to another hook, wp_ajax_nopriv_(action).

Example AJAX code for the frontend

<?php

add_action( 'wp_enqueue_scripts', 'myajax_data', 99 );
function myajax_data(){

	wp_localize_script( 'twentyfifteen-script', 'myajax',
		array(
			'url' => admin_url('admin-ajax.php')
		)
	);

}

add_action( 'wp_footer', 'my_action_javascript', 99 ); // for the frontend
function my_action_javascript() {
	?>
	<script type="text/javascript" >
	jQuery(document).ready(function($) {
		var data = {
			action: 'my_action',
			whatever: 1234
		};

		// 'ajaxurl' is not defined in the frontend, so we added its equivalent using wp_localize_script()
		jQuery.post( myajax.url, data, function(response) {
			alert('Received from the server: ' + response);
		});
	});
	</script>
	<?php
}

add_action( 'wp_ajax_my_action', 'my_action_callback' );
add_action( 'wp_ajax_nopriv_my_action', 'my_action_callback' );
function my_action_callback() {
	$whatever = intval( $_POST['whatever'] );

	echo $whatever + 10;

	// exiting is necessary so that there is nothing unnecessary in the response, only what the function returns
	wp_die();
}

The code is designed for the twentyfifteen theme. You can insert the code in the theme's functions.php.

This code will work for any theme, the only thing needed for this is to change the name of the main theme script twentyfifteen-script, which is included after jquery.

Logical Connection of AJAX Hooks

I didn't complicate the reading and didn't explain how to properly connect AJAX through hooks in the code. However, everything written below is not mandatory, because it will work anyway, but it is recommended.

The handling functions attached to the hooks:

  • wp_ajax_(action)
  • wp_ajax_nopriv_(action)

Both hooks always satisfy the condition wp_doing_ajax():

if( wp_doing_ajax() ){}

// before WP 4.7
if( defined('DOING_AJAX') ){}

This means that the hooks should only be connected when this condition is met.

By using this rule, hooks do not need to be connected where it doesn't make sense. For example, when generating a template page or an admin page. This small detail adds more logic to the code and in some cases can eliminate bugs.

Here's an example of how to properly connect all AJAX hooks.

// connect AJAX handlers only when it makes sense
if( wp_doing_ajax() ){
	add_action( 'wp_ajax_myaction', 'ajax_handler' );
	add_action( 'wp_ajax_nopriv_myaction', 'ajax_handler' );
}

// or before WP 4.7
if( defined('DOING_AJAX') ){
	add_action( 'wp_ajax_myaction', 'ajax_handler' );
	add_action( 'wp_ajax_nopriv_myaction', 'ajax_handler' );
}

In this case, the hooks will only be connected during an AJAX request and will not be connected when simply visiting the frontend, admin panel, REST, or CRON request.

Also, remember that data sent from the frontend to the file wp-admin/admin-ajax.php is handled by an arbitrary function specified in the ajax_handler() hook, regardless of whether the user is authorized or not.

Security: Use Nonces and Check Permissions

There is no urgent need to check an AJAX request if it is potentially not dangerous. For example, when it simply retrieves data. But when a request deletes or updates data, it must be additionally protected using nonce code and permission checks.

Developers often neglect to add such protection, leading to unexpected results. Malicious users may somehow manipulate an authorized user to do what they need, ultimately causing harm to the site you've been working on for months or years.

There are two types of protection that should be used in most cases for AJAX requests.

1. Nonce code (random code)

Nonce is a unique string that is created and used only once - one-time code. Nonce verification is used when it is necessary to ensure that the request was sent from the specified "location".

In WordPress, there are functions wp_create_nonce() and check_ajax_referer() - these are basic functions for creating and subsequently checking the nonce code. With their help, we will create nonce protection for AJAX requests.

First, let's create a nonce code:

add_action( 'wp_enqueue_scripts', 'myajax_data', 99 );
function myajax_data(){

	wp_localize_script( 'twentyfifteen-script', 'myajax',
		array(
			'url' => admin_url('admin-ajax.php'),
			'nonce' => wp_create_nonce('myajax-nonce')
		)
	);

}

twentyfifteen-script is the name of the main theme script (see above), which is included on the site using wp_enqueue_script().

Then, in the AJAX request, we will add a variable with the nonce code:

var ajaxdata = {
	action     : 'myajax-submit',
	nonce_code : myajax.nonce
};
jQuery.post( myajax.url, ajaxdata, function( response ) {
	alert( response );
});

Now, in the request processing, it is necessary to check the nonce code:

add_action( 'wp_ajax_nopriv_myajax-submit', 'myajax_submit' );
add_action( 'wp_ajax_myajax-submit', 'myajax_submit' );
function myajax_submit(){
	// check the nonce code, if the check fails, stop processing
	check_ajax_referer( 'myajax-nonce', 'nonce_code' );
	// or like this
	if( ! wp_verify_nonce( $_POST['nonce_code'], 'myajax-nonce' ) ) die( 'Stop!');

	// process the data and return
	echo 'Returned data';

	// don't forget to end PHP
	wp_die();
}

check_ajax_referer() is based on the function wp_verify_nonce() and essentially is its wrapper for AJAX requests.

Note that in this case the Nonce code is created in the HTML code. This means that if you have a page caching plugin installed, this code may, and most likely will, become outdated by the time of the next AJAX request, because HTML code is cached...

2. Access rights check

Here, AJAX requests will only work for users with the specified capability, for example author. For all others, including unauthorized users, the AJAX request will return an error.

The peculiarity here is that unauthorized users should also see an error message when making an AJAX request. Therefore, it is necessary to handle the request for them as well and return an error message:

add_action( 'wp_ajax_nopriv_myajax-submit', 'myajax_submit' );
add_action( 'wp_ajax_myajax-submit', 'myajax_submit' );
function myajax_submit(){
	// check the nonce code, if the check fails, stop processing
	check_ajax_referer( 'myajax-nonce', 'nonce_code' );

	// the current user does not have the author or higher capability
	if( ! current_user_can('publish_posts') )
		die('This request is available to users with the author or higher capability.')

	// OK. The user has the necessary capabilities!

	// Do what is needed and display the data on the screen to return it to the script

	// Don't forget to exit
	wp_die();
}

Enabling caching for AJAX requests

By default, all AJAX requests are NOT cached by the browser, for this PHP sets special headers using the function nocache_headers().

Most of the time, there is no need to cache AJAX requests, as they should return fresh data, but there are cases when such caching can save resources and increase the script's speed. For example, if we have a complex product filter that users constantly use. It would be reasonable to cache all filter results, for example for a couple of hours, as products are not added at such a rapid pace...

How to enable caching for specified AJAX requests can be found in the second example of the function nocache_headers().

Catching bugs, PHP errors

Problems may arise during an AJAX request and the appearance of PHP errors. Notices or messages can alter the returned result or cause a JavaScript error.

Debugging (displaying errors on the screen)

Option:

Usually, requests are sent from the browser to a file. So, to see the result of the request, an error, or anything else, you can open the developer panel, select our request from many, and see what it returned.

In the code, you can use familiar functions such as print_r() or var_dump() to see what is in the necessary variables.

Option: displaying errors in AJAX requests

By default, WordPress does not display errors for AJAX requests even if the WP_DEBUG constant is enabled! This can be seen in the code of the function wp_debug_mode().

Despite this, such display can be enabled, as on production projects we have WP_DEBUG disabled anyway and have nothing to fear, but catching bugs helps us tremendously!

To enable error display for AJAX requests, you need to insert the following code into the theme file functions.php or into a plugin. But it is best to insert it as early as possible to see early errors, preferably in MU plugins...

if( WP_DEBUG && WP_DEBUG_DISPLAY && (defined('DOING_AJAX') && DOING_AJAX) ){
	@ ini_set( 'display_errors', 1 );
}

Option: outputting data to a log file

If, during the code writing process, you need to look into the variable $myvar, you can also use the following code in the AJAX request handler:

error_log( print_r($myvar, true) );

As a result, the contents of the variable $myvar will be written to the server's log file (error.log). This way, you can execute an AJAX request and look into the log.

Option: logging PHP errors to a log file

To log PHP notices and errors to a log file, you need to enable the constant WP_DEBUG_LOG. Such a log file will appear in the wp-content folder.

Option:

If you cannot see the error message and need to work in developer mode, you can clear the buffer immediately before returning the data:

ob_clean();
echo $whatever;
die();

After that, you need to see what the request returns through the browser debugger or in some other way...

Option:

Also, for debugging, you can use the FirePHP tool, which logs errors to the browser console.

Error when returning data

If an AJAX request to the file wp-admin/admin-ajax.php failed, it will return the response -1 or 0.

  • -1 - error during the request check. See the function check_ajax_referer()
  • 0 - the request processing returned an empty result
  • 0 - is also returned by default in all other cases.

Plugins

The AJAX Simply plugin - adds a class that allows you to conveniently and quickly write AJAX requests on the client side and handle responses on the server side.