Choosing Between WP REST API and admin-ajax.php for WordPress AJAX Requests
A decision guide comparing WP REST API and admin-ajax.php for WordPress AJAX, with a table of trade‑offs, sample code for registering a REST endpoint, and verification steps.
26 Aug 2025, 17:24 UTC

Decision and constraints
When you need to add AJAX functionality to a WordPress theme or plugin you must decide whether to use the WP REST API or the legacy admin-ajax.php endpoint. The decision hinges on the following constraints:
- Minimum WordPress version you support (REST API requires 4.7+).
- Desired data format (JSON is preferred for modern front‑ends).
- Authentication method you can reliably implement (nonce‑based cookie auth is built‑in for REST).
- Need for extensibility, versioning, and future‑proofing.
- Performance tolerance for the small routing overhead introduced by the REST API.
Comparison of supported options
| Feature | WP REST API | admin-ajax.php |
|---|---|---|
| Introduced | WP 4.7 | WP 2.0+ |
| Data format | JSON (standard) | HTML/plain/text (via echo) |
| Extensibility | Easy via register_rest_route | Limited to action hooks |
| Authentication | Built‑in cookie nonce, JWT plugins | Relies on check_ajax_referer |
| Performance | Slightly higher overhead due to routing | Minimal overhead |
| Future‑proof | Actively developed, core focus | Legacy, discouraged for new code |
Trade‑offs
The REST API provides a standardized, extensible interface that works well for single‑page applications, mobile apps, and any consumer that expects JSON. It requires you to register routes, handle nonces via the X-WP-Nonce header, and consider CORS if the request originates from a different domain. The overhead is usually negligible on a typical PHP 7.4+ server.
Using admin-ajax.php is a drop‑in solution that works on any WordPress version, has virtually no routing cost, and only needs a nonce check via check_ajax_referer. However, it lacks a structured API, makes versioning difficult, and is discouraged by WordPress coding standards for new development.
Concrete implementation
Enqueue the script and localize data
Place this code in your plugin or theme’s functions.php (or a dedicated bootstrap file). It registers a REST endpoint, localizes the script with the base URL and a nonce, and enqueues the JavaScript.
function myplugin_register_rest_routes() {
register_rest_route( 'myplugin/v1', '/search/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => 'myplugin_rest_search_callback',
'permission_callback' => function () {
return current_user_can( 'read' );
},
) );
}
add_action( 'rest_api_init', 'myplugin_register_rest_routes' );
function myplugin_rest_search_callback( WP_REST_Request $request ) {
$search = sanitize_text_field( $request->get_param( 's' ) );
$args = array(
's' => $search,
'posts_per_page' => 10,
'post_type' => 'post',
);
$query = new WP_Query( $args );
$results = wp_list_pluck( $query->posts, 'post_title' );
return new WP_REST_Response( $results, 200 );
}
function myplugin_enqueue_assets() {
wp_enqueue_script(
'myplugin-ajax',
plugin_dir_url( __FILE__ ) . 'js/ajax.js',
array( 'jquery' ),
'1.0',
true
);
wp_localize_script( 'myplugin-ajax', 'mypluginAjax', array(
'restUrl' => esc_url_raw( rest_url( 'myplugin/v1/search/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
) );
}
add_action( 'wp_enqueue_scripts', 'myplugin_enqueue_assets' );
Client‑side fetch with nonce header
Save the following as js/ajax.js. It sends a GET request, includes the nonce in the X-WP-Nonce header, and logs the JSON response.
document.addEventListener( 'DOMContentLoaded', function () {
const input = document.getElementById( 'search-input' );
if ( !input ) return;
input.addEventListener( 'keyup', function (e) {
const term = e.target.value.trim();
if ( term.length < 2 ) return;
fetch( mypluginAjax.restUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': mypluginAjax.nonce,
},
body: JSON.stringify({ s: term })
})
.then( response => {
if ( !response.ok ) throw new Error( 'HTTP ' + response.status );
return response.json();
})
.then( data => {
console.log( 'Search results:', data );
// Update UI with data
})
.catch( err => console.error( 'AJAX error:', err ) );
});
});
Fallback to admin‑ajax.php (optional)
If you need to support WordPress < 4.7, you can add a fallback that uses the traditional admin‑ajax endpoint. Register an action and adjust the JavaScript to try the REST URL first, then admin‑ajax.
function myplugin_ajax_search_fallback() {
check_ajax_referer( 'myplugin_nonce', 'security' );
$search = sanitize_text_field( $_POST['s'] );
$args = array(
's' => $search,
'posts_per_page' => 10,
'post_type' => 'post',
);
$query = new WP_Query( $args );
$titles = wp_list_pluck( $query->posts, 'post_title' );
wp_send_json_success( $titles );
}
add_action( 'wp_ajax_myplugin_search', 'myplugin_ajax_search_fallback' );
add_action( 'wp_ajax_nopriv_myplugin_search', 'myplugin_ajax_search_fallback' );
In the JavaScript, after a failed REST request you could POST to ajaxurl with action myplugin_search and nonce myplugin_nonce.
Verification steps
- Load a page that enqueues the script, open the browser console, and type a search term of at least two characters.
- Confirm that the network request to
/wp-json/myplugin/v1/search/?s=…returns200 OKand aContent-Type: application/jsonheader. - Alter or remove the
X-WP-Nonceheader in the request (e.g., via devtools) and verify that the response is401 Unauthorized. - Compare the timing of the REST call with an equivalent
admin-ajax.phprequest (same search term) using the Network tab; the difference should be under ~10 ms on a typical PHP 7.4+ server. - If you implemented the fallback, temporarily rename the REST route file or block
/wp-json/via a security plugin and ensure the request still succeeds viaadmin-ajax.php.
Limitations
- The REST API introduces a small routing overhead; on extremely low‑end shared hosts this could be measurable.
- Some security or performance plugins may block or throttle the
/wp-json/route; test in a staging environment with your full plugin stack active. - For high‑frequency AJAX (e.g., keystroke search) implement debounce or throttling on the client side to avoid excessive server load.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.