How to Handle AJAX Requests in WordPress with wp_ajax

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.