A reusable WordPress AJAX handler class

A client running a big WooCommerce store called me. They had a nice feature where you could add custom text to a product and the price would update on the fly, no page reload. A classic WordPress AJAX handler job. The problem? It was broken half the time. Users kept getting that infamous “0” error WordPress spits out when an AJAX call dies. Under the hood it was a mess: one giant, unreadable function in their theme’s functions.php file.

My first thought was to just refactor the existing function: add a proper nonce check for security, sanitize the input, make it readable. Simple, right? Then the client dropped the bomb: “Once this is fixed, can we add it to the quick view modal on the category pages?” That changed things. Cleaning up the function was no longer the right move, because copy-pasting that logic into another spot would be a future headache. The only sane way forward was to build a proper, reusable class. A little more work upfront to save hours of pain later.

Your WordPress AJAX handler shouldn’t be a lone function

Shoving AJAX logic into a single function is a trap. It feels fast, but it doesn’t scale and it’s a pain to maintain. You end up with security holes because you forgot a nonce check, or you duplicate the same code all over the place. A simple class forces a better structure. It makes you think about the separate parts of the process: registering the script, passing data securely, and handling the request itself. This approach is heavily inspired by a post I read years ago on carlalexander.ca, which really cleaned up how I handle these jobs.

The idea is to create a handler that reliably does three things:

  • Set up and secure: it registers the JavaScript and uses wp_localize_script to pass over the ajax_url and a security nonce. This part is not optional.
  • Execute: it needs a dedicated method to hold the actual logic that runs when the request comes in.
  • Respond: it sends back a response, usually as JSON, and then terminates the request properly with wp_die().

A simple, reusable AJAX handler class

Here’s the basic structure I use. It’s organized and keeps everything in one place, so no more hunting through functions.php.

<?php
namespace MyPlugin;

class AjaxHandler {

    public static function register() {
        $instance = new self();
        // Hook for logged-in users
        add_action( 'wp_ajax_my_plugin_action', [ $instance, 'handle_request' ] );
        // Hook for logged-out users (optional)
        add_action( 'wp_ajax_nopriv_my_plugin_action', [ $instance, 'handle_request' ] );
        // Enqueue scripts
        add_action( 'wp_enqueue_scripts', [ $instance, 'enqueue_assets' ] );
    }

    public function enqueue_assets() {
        // You'd have a real path here, of course.
        wp_register_script( 'my-plugin-ajax', plugins_url( '/js/my-ajax.js', __FILE__ ), [ 'jquery' ] );

        // Pass data to the script
        wp_localize_script( 'my-plugin-ajax', 'my_plugin_ajax_obj', [
            'ajax_url'    => admin_url( 'admin-ajax.php' ),
            'nonce'       => wp_create_nonce( 'my_plugin_nonce' ),
            'some_value'  => 'Here is some data from PHP'
        ] );

        wp_enqueue_script( 'my-plugin-ajax' );
    }

    public function handle_request() {
        // 1. Security First! Always.
        if ( ! check_ajax_referer( 'my_plugin_nonce', 'nonce', false ) ) {
            wp_send_json_error( 'Invalid nonce.' );
        }

        // 2. Sanitize your inputs
        $some_data = isset( $_POST['some_data'] ) ? sanitize_text_field( $_POST['some_data'] ) : '';

        // 3. Do your work...
        // ...for example, update post meta, send an email, etc.
        $response_data = [ 'message' => 'Success! We received: ' . $some_data ];

        // 4. Send a response and die.
        wp_send_json_success( $response_data );
    }
}

// Kick it all off
AjaxHandler::register();

Why bother with a class?

Writing code like this isn’t about being fancy, it’s about being professional. It’s about building something that won’t make you want to pull your hair out six months from now when a client asks for a small change. By splitting the concerns into separate methods, one for enqueuing and one for handling the request, you create a pattern that is easier to debug, extend, and hand off to another developer. You stop writing throwaway code and start building things you can rely on.

This stuff gets complicated fast. If you are tired of debugging someone else’s mess and you just want your site to work, drop my team a line. We have probably seen it before.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.