WordPress has a built-in AJAX system that routes all requests through wp-admin/admin-ajax.php. The pattern is simple: register a PHP handler with wp_ajax_{action} (for logged-in users) and wp_ajax_nopriv_{action} (for everyone), then send the request from JavaScript using the ajaxurl variable that WordPress provides in the admin, or that you localize to the front end.
Problem: How do you handle an AJAX request in WordPress so it respects the nonce, supports both logged-in and logged-out users, and uses the proper endpoint?
Solution: Register a PHP callback with add_action('wp_ajax_my_action') for logged-in users and add_action('wp_ajax_nopriv_my_action') for everyone else. Verify the nonce with check_ajax_referer(), process the request, and respond with wp_send_json_success() or wp_send_json_error().
PHP side — register the handler:
// Logged-in users
add_action( 'wp_ajax_get_post_views', 'ajax_get_post_views' );
// Guests / non-logged-in users
add_action( 'wp_ajax_nopriv_get_post_views', 'ajax_get_post_views' );
function ajax_get_post_views() {
check_ajax_referer( 'post_views_nonce', 'nonce' );
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
if ( ! $post_id ) {
wp_send_json_error( [ 'message' => 'Invalid post ID.' ] );
}
$views = (int) get_post_meta( $post_id, '_post_views', true );
update_post_meta( $post_id, '_post_views', $views + 1 );
wp_send_json_success( [ 'views' => $views + 1 ] );
}
Localize the AJAX URL and nonce for front-end scripts:
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_script( 'my-ajax', get_template_directory_uri() . '/js/ajax.js', [ 'jquery' ], '1.0', true );
wp_localize_script( 'my-ajax', 'MyAjax', [
'url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'post_views_nonce' ),
] );
} );
JavaScript side — send the request:
jQuery( function( $ ) {
$.post( MyAjax.url, {
action: 'get_post_views',
nonce: MyAjax.nonce,
post_id: myPostId,
}, function( response ) {
if ( response.success ) {
$( '.view-count' ).text( response.data.views );
} else {
console.error( response.data.message );
}
} );
} );
NOTE: Always verify a nonce with check_ajax_referer() at the top of every AJAX handler. Skip this and any visitor can call your endpoint arbitrarily. Use wp_send_json_success() and wp_send_json_error() — they set the correct Content-Type: application/json header and call die() for you.