Skip to main content
WP HealthKit

WordPress admin-ajax vs REST API: Complete Comparison

April 11, 202615 min readTutorialsBy Jamie

If you've been developing WordPress plugins for more than a few years, you've probably used admin-ajax.php—that magical endpoint that handles asynchronous requests and dynamic content loading. But if you're building new plugins today, you're likely wondering whether to stick with admin-ajax or embrace the WordPress REST API. This decision impacts security, performance, maintainability, and scalability far more than most developers realize.

The shift from admin-ajax.php to the WordPress REST API represents a fundamental architectural evolution in WordPress plugin development. Understanding when to use each approach, how they differ in performance and security, and how to migrate existing code is essential for building modern, secure WordPress plugins that work well with WP HealthKit's security scanning capabilities.

Table of Contents

  1. What is admin-ajax.php and How It Works
  2. Understanding the WordPress REST API
  3. Performance Comparison and Benchmarks
  4. Security Considerations and Best Practices
  5. When to Use admin-ajax vs REST API
  6. Practical Code Examples for Both Approaches
  7. Migration Path: Moving from admin-ajax to REST API
  8. Frequently Asked Questions

What is admin-ajax.php and How It Works

The admin-ajax.php endpoint is a legacy mechanism for handling asynchronous requests in WordPress. When you make an AJAX request through jQuery, it typically goes to /wp-admin/admin-ajax.php with a action parameter that determines which callback function executes.

Here's how a traditional admin-ajax implementation works:

// In your plugin file
add_action( 'wp_ajax_my_action', 'my_ajax_handler' );
add_action( 'wp_ajax_nopriv_my_action', 'my_ajax_handler' );

function my_ajax_handler() {
    check_ajax_referer( 'my_nonce' );
    
    $data = sanitize_text_field( $_POST['data'] );
    
    // Process the request
    $result = process_data( $data );
    
    wp_send_json_success( $result );
}

The frontend code would look like this:

jQuery.post(
    '/wp-admin/admin-ajax.php',
    {
        action: 'my_action',
        data: 'user input',
        nonce: myNonce
    },
    function( response ) {
        console.log( response );
    }
);

The wp_ajax_ prefix handles authenticated requests (from logged-in users), while wp_ajax_nopriv_ handles unauthenticated requests. The endpoint always requires a nonce for security, which is a random token that prevents CSRF attacks.

Admin-ajax.php is remarkably flexible—you can send any data, return any format, and it requires minimal setup. This simplicity made it the standard for WordPress AJAX for years. However, this flexibility comes with trade-offs in discoverability, standardization, and modern development practices.

Understanding the WordPress REST API

The WordPress REST API is a modern HTTP-based API introduced in WordPress 4.7. It provides RESTful endpoints that follow HTTP conventions: GET for reading data, POST for creating, PUT/PATCH for updating, and DELETE for removing. Each endpoint is self-documenting, standardized, and discoverable.

A simple REST endpoint looks like this:

register_rest_route(
    'my-plugin/v1',
    '/process',
    array(
        'methods'             => 'POST',
        'callback'            => 'my_rest_handler',
        'permission_callback' => 'my_rest_permission_check',
        'args'                => array(
            'data' => array(
                'type'              => 'string',
                'sanitize_callback' => 'sanitize_text_field',
                'required'          => true,
            ),
        ),
    )
);

function my_rest_handler( $request ) {
    $data = $request->get_param( 'data' );
    
    // Process the request
    $result = process_data( $data );
    
    return new WP_REST_Response( $result, 200 );
}

function my_rest_permission_check( $request ) {
    return current_user_can( 'manage_options' );
}

The frontend request is cleaner and more standard:

fetch( '/wp-json/my-plugin/v1/process', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': wpNonce
    },
    body: JSON.stringify( { data: 'user input' } )
} )
.then( response => response.json() )
.then( data => console.log( data ) );

The REST API automatically validates parameters, sanitizes input, and enforces capability checks. It handles nonce authentication through headers, and the entire API is discoverable via /wp-json/—clients can understand endpoints without documentation.

Performance Comparison and Benchmarks

When deciding between admin-ajax and REST API, performance is a critical factor. Let's look at how they compare.

Load Time: The REST API is slightly heavier initially because it includes the full WordPress bootstrap and REST server infrastructure. However, the difference is typically 20-50ms for most requests, which is negligible for user experience.

Response Size: Both approaches send similar response sizes when returning JSON. The REST API sometimes includes additional metadata about the response, but this is rarely significant.

Database Queries: The key performance difference emerges in database query counts. The REST API's built-in validation and permission checking can result in additional queries if not carefully implemented. However, both approaches allow for optimization—the difference is whether you want to optimize or not.

Here's a realistic benchmark for a typical plugin processing user data:

admin-ajax.php:
  - Load time: 45ms
  - Database queries: 4
  - Response size: 2.3KB

REST API:
  - Load time: 55ms
  - Database queries: 5
  - Response size: 2.5KB

For most use cases, this ~10ms difference is immaterial. However, if you're building high-volume endpoints processing thousands of requests per second, admin-ajax might have a slight edge. But even then, proper caching and optimization matter far more than the fundamental approach.

Security Considerations and Best Practices

This is where the REST API and admin-ajax differ most significantly. Security should drive your decision as much as functionality.

Nonce Handling: Admin-ajax requires manual nonce verification with check_ajax_referer(). If you forget this single line, your endpoint is vulnerable to CSRF attacks. The REST API includes built-in nonce support and validates it automatically—but only if you configure it properly:

register_rest_route(
    'my-plugin/v1',
    '/secure-action',
    array(
        'methods'             => 'POST',
        'callback'            => 'my_handler',
        'permission_callback' => function() {
            return current_user_can( 'edit_posts' );
        },
    )
);

Input Validation: The REST API's args parameter allows you to declare all parameters with their type, validation rules, and sanitization callbacks. This is enforced automatically:

register_rest_route(
    'my-plugin/v1',
    '/validate-example',
    array(
        'methods'  => 'POST',
        'callback' => 'handle_request',
        'args'     => array(
            'email' => array(
                'type'              => 'string',
                'format'            => 'email',
                'sanitize_callback' => 'sanitize_email',
                'required'          => true,
            ),
            'age' => array(
                'type'              => 'integer',
                'minimum'           => 0,
                'maximum'           => 150,
                'sanitize_callback' => 'absint',
            ),
        ),
    )
);

With admin-ajax, you're responsible for manually validating every parameter:

function my_ajax_handler() {
    check_ajax_referer( 'my_nonce' );
    
    // You have to manually validate everything
    if ( ! isset( $_POST['email'] ) || ! is_email( $_POST['email'] ) ) {
        wp_send_json_error( 'Invalid email' );
    }
    
    $email = sanitize_email( $_POST['email'] );
    $age = isset( $_POST['age'] ) ? absint( $_POST['age'] ) : 0;
    
    // More validation code...
}

Authentication: The REST API respects WordPress authentication methods—cookies, JWT tokens, and application passwords. Admin-ajax only uses WordPress cookies, limiting integration with modern authentication systems.

CORS Support: The REST API understands CORS headers and properly handles preflight requests. Admin-ajax requires manual CORS header handling if you need cross-origin requests.

WP HealthKit scans plugins for both admin-ajax and REST API endpoints, checking for proper nonce validation, input validation, and capability checks. Plugins relying on admin-ajax are at higher risk of failing security audits because more of the security burden falls on the developer.

When to Use admin-ajax vs REST API

Use the REST API if:

  • You're building new functionality today
  • Your plugin needs to be discoverable by other systems
  • You want standardized, self-documenting endpoints
  • You need better integration with modern frontend frameworks (React, Vue)
  • Security and validation automation matter to you
  • You want to minimize manual security implementation

Use admin-ajax if:

  • You're maintaining legacy code and migration isn't worth the effort
  • Your endpoint is very simple and doesn't justify REST infrastructure overhead
  • You need backward compatibility with very old WordPress versions (pre-4.7)
  • You're building something quick and temporary that won't be maintained long-term

In practice, for any production plugin being published to wp.org or used in enterprise environments, the REST API is the clear choice. The security benefits alone justify the minimal additional complexity.

Practical Code Examples for Both Approaches

Let's build the same feature—allowing users to save preferences—using both approaches.

Admin-ajax approach:

add_action( 'wp_ajax_save_preferences', 'save_user_preferences_ajax' );
add_action( 'wp_ajax_nopriv_save_preferences', 'save_user_preferences_ajax' );

function save_user_preferences_ajax() {
    check_ajax_referer( 'preferences_nonce' );
    
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( 'Insufficient permissions', 403 );
    }
    
    $user_id = get_current_user_id();
    if ( ! $user_id ) {
        wp_send_json_error( 'Not logged in', 401 );
    }
    
    if ( ! isset( $_POST['theme'] ) ) {
        wp_send_json_error( 'Missing theme parameter' );
    }
    
    $theme = sanitize_key( $_POST['theme'] );
    $allowed_themes = array( 'light', 'dark', 'auto' );
    
    if ( ! in_array( $theme, $allowed_themes, true ) ) {
        wp_send_json_error( 'Invalid theme value' );
    }
    
    update_user_meta( $user_id, 'my_plugin_theme', $theme );
    
    wp_send_json_success( array(
        'message' => 'Preferences saved',
        'theme'   => $theme,
    ) );
}

// In your plugin's main file or settings page
wp_localize_script( 'my-script', 'myPlugin', array(
    'ajaxUrl' => admin_url( 'admin-ajax.php' ),
    'nonce'   => wp_create_nonce( 'preferences_nonce' ),
) );

REST API approach:

register_rest_route(
    'my-plugin/v1',
    '/preferences',
    array(
        'methods'             => 'POST',
        'callback'            => 'save_user_preferences_rest',
        'permission_callback' => function( $request ) {
            return current_user_can( 'manage_options' );
        },
        'args'                => array(
            'theme' => array(
                'type'              => 'string',
                'enum'              => array( 'light', 'dark', 'auto' ),
                'sanitize_callback' => 'sanitize_key',
                'required'          => true,
            ),
        ),
    )
);

function save_user_preferences_rest( $request ) {
    $user_id = get_current_user_id();
    $theme = $request->get_param( 'theme' );
    
    update_user_meta( $user_id, 'my_plugin_theme', $theme );
    
    return new WP_REST_Response( array(
        'message' => 'Preferences saved',
        'theme'   => $theme,
    ), 200 );
}

// In your frontend code
fetch( '/wp-json/my-plugin/v1/preferences', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': wpNonce
    },
    body: JSON.stringify( { theme: 'dark' } )
} );

Notice how much cleaner the REST API code is? The permission check, parameter validation, and sanitization are declarative. The admin-ajax version requires significantly more manual validation code, increasing the risk of bugs or security oversights.

Migration Path: Moving from admin-ajax to REST API

If you have existing admin-ajax endpoints, migrating to REST API is straightforward.

Step 1: Register REST routes for each admin-ajax action:

// Old admin-ajax
add_action( 'wp_ajax_get_data', 'handle_get_data' );

// New REST equivalent
register_rest_route(
    'my-plugin/v1',
    '/data',
    array(
        'methods'             => 'GET',
        'callback'            => 'handle_get_data_rest',
        'permission_callback' => '__return_true',
    )
);

Step 2: Update frontend requests:

// Old admin-ajax
jQuery.post( '/wp-admin/admin-ajax.php', {
    action: 'get_data',
    nonce: myNonce
}, function( response ) {
    console.log( response );
});

// New REST API
fetch( '/wp-json/my-plugin/v1/data', {
    headers: { 'X-WP-Nonce': wpNonce }
})
.then( r => r.json() )
.then( data => console.log( data ) );

Step 3: Maintain backward compatibility temporarily:

You can run both in parallel during a transition period. Keep the old admin-ajax endpoints functional while encouraging clients to use the REST API. Eventually, deprecate and remove the old endpoints.

Step 4: Test thoroughly:

Use WP HealthKit to audit both the old and new implementations. The REST API should pass more security checks because validation is handled automatically.

Broader Context and Best Practices

Step-by-step tutorials for WordPress plugin development serve a critical role in the ecosystem by bridging the gap between documentation and practical implementation. WordPress.org documentation explains what functions are available, but tutorials show how to combine them into working solutions. This practical knowledge is especially valuable for patterns that span multiple WordPress subsystems, such as building a custom REST API endpoint that validates input, checks permissions, queries the database, and returns properly formatted responses. Each step involves different WordPress APIs that must work together correctly.

The most effective WordPress development tutorials teach not just the how but the why behind each decision. Understanding why WordPress uses nonces instead of simpler tokens, why capability checks should test specific capabilities rather than roles, or why prepared statements matter more than escaping for SQL queries gives developers the foundation to make good decisions when they encounter situations that tutorials haven't covered. This deeper understanding is what separates developers who can follow instructions from developers who can architect secure, maintainable solutions.

Testing and validation are often the most overlooked aspects of WordPress plugin tutorials, yet they are arguably the most important. A tutorial that shows how to build a feature without showing how to verify it works correctly and handles edge cases teaches only half the lesson. Modern WordPress development tutorials should include PHPUnit test examples, WP-CLI test commands, and browser testing strategies alongside the implementation code. This testing-first mindset helps developers build confidence in their code and catch regressions before they reach production.

The WordPress developer community's shift toward more professional development practices has elevated the expectations for plugin quality significantly. Practices like dependency management with Composer, automated testing with PHPUnit, continuous integration with GitHub Actions, and static analysis with PHPStan were once considered optional extras. They are now expected baseline practices for serious plugin development. Understanding these tools and how they integrate into the WordPress development workflow is essential knowledge for any developer building plugins that others will rely on.

Broader Context and Best Practices

Step-by-step tutorials for WordPress plugin development serve a critical role in the ecosystem by bridging the gap between documentation and practical implementation. WordPress.org documentation explains what functions are available, but tutorials show how to combine them into working solutions. This practical knowledge is especially valuable for patterns that span multiple WordPress subsystems, such as building a custom REST API endpoint that validates input, checks permissions, queries the database, and returns properly formatted responses. Each step involves different WordPress APIs that must work together correctly.

The most effective WordPress development tutorials teach not just the how but the why behind each decision. Understanding why WordPress uses nonces instead of simpler tokens, why capability checks should test specific capabilities rather than roles, or why prepared statements matter more than escaping for SQL queries gives developers the foundation to make good decisions when they encounter situations that tutorials haven't covered. This deeper understanding is what separates developers who can follow instructions from developers who can architect secure, maintainable solutions.

Testing and validation are often the most overlooked aspects of WordPress plugin tutorials, yet they are arguably the most important. A tutorial that shows how to build a feature without showing how to verify it works correctly and handles edge cases teaches only half the lesson. Modern WordPress development tutorials should include PHPUnit test examples, WP-CLI test commands, and browser testing strategies alongside the implementation code. This testing-first mindset helps developers build confidence in their code and catch regressions before they reach production.

Frequently Asked Questions

Is the REST API slower than admin-ajax.php?

Negligibly slower—we're talking about 5-20ms differences. For most use cases, the difference is completely imperceptible to users. Proper caching and query optimization matter far more than the fundamental approach.

Can I use both in the same plugin?

Absolutely. Many plugins transition by supporting both simultaneously, with REST API as the recommended approach and admin-ajax for backward compatibility. Eventually, deprecate admin-ajax once clients have migrated.

Does the REST API require authentication?

The REST API respects WordPress authentication—if a user is logged in (cookie-authenticated), requests work. If not, you need to send an X-WP-Nonce header. You can also use application passwords or JWT tokens for more sophisticated authentication.

How do I handle file uploads with the REST API?

The REST API supports file uploads through the standard WordPress REST file upload mechanism. Use the WP_REST_Request object's file handling:

'args' => array(
    'image' => array(
        'type'     => 'object',
        'required' => true,
        'properties' => array(
            'name'     => array( 'type' => 'string' ),
            'content'  => array( 'type' => 'string' ),
            'size'     => array( 'type' => 'integer' ),
        ),
    ),
)

Can admin-ajax.php endpoints be discovered like REST API endpoints?

No, admin-ajax endpoints are private by nature. You must document them. REST API endpoints are self-documenting—clients can discover them via /wp-json/ and understand parameters, methods, and responses.

What about CORS issues with admin-ajax?

Admin-ajax requires manual CORS header handling. The REST API includes built-in CORS support, automatically handling cross-origin requests according to WordPress security policy.

Is there a performance difference with high volume?

At extremely high volume (thousands of requests per second), admin-ajax might have a slight advantage due to less overhead. However, proper caching, CDN configuration, and query optimization have far more impact than the fundamental approach.

Conclusion

The WordPress REST API represents the modern standard for plugin development. While admin-ajax.php remains functional and useful for legacy codebases, new plugins should prioritize REST endpoints. The security automation, standardized validation, and better integration with modern frameworks make the REST API the clear choice for production plugins.

The choice between admin-ajax and REST API reflects broader shifts in how developers approach plugin architecture. Modern WordPress development embraces structured APIs, standardization, and separation of concerns. The REST API encourages these patterns. Admin-ajax.php, while powerful and flexible, is older infrastructure that requires more manual work to secure and maintain.

For plugin developers prioritizing long-term maintainability, security, and compatibility with modern development practices, the REST API is the clear path forward. The small additional complexity of registering routes and handling responses is worth the security and maintainability gains. For sites with extensive legacy code, admin-ajax.php support might be necessary temporarily, but should be viewed as infrastructure being actively migrated toward REST APIs.

Before publishing or deploying your plugin, security auditing is essential. Upload your plugin to WP HealthKit to scan for both admin-ajax and REST API security issues, ensuring proper nonce validation, capability checks, and input handling.

For additional guidance, explore the WordPress REST API Handbook and WordPress's AJAX documentation. You might also find our guides on WordPress REST API Security and WordPress Nonces and CSRF Protection helpful, plus WP HealthKit's plugin directory to see how production plugins handle these patterns.

Ready to audit your plugin?

WP HealthKit checks for all the issues in this article and 40+ more across 46 verification layers.

Comments

WordPress admin-ajax vs REST API: Complete Comparison | WP HealthKit