The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →To use AJAX in WordPress, load a script through WordPress, pass it the site’s complete admin-ajax.php URL and a nonce, send an action value, then handle that action with matching PHP hooks. Verify the nonce, check the user’s capability, validate the input, and return JSON. For a reusable, resource-oriented interface, register a REST API route instead.
Antihost offers managed WordPress hosting with free migration, one-click staging and a 14-day free trial.
What AJAX does in WordPress
AJAX lets browser code send a request and update part of the current page without a full-page reload. A search suggestion list, cart quantity, “load more” button, inline form, vote control, or admin setting can therefore change while the surrounding page remains in place. WordPress commonly returns JSON, even though the historical name stands for Asynchronous JavaScript and XML.
As an Amazon Associate I earn from qualifying purchases.
There are two practical interfaces:
| Interface | How WordPress dispatches it | Best fit | Typical authentication | Response and reuse |
|---|---|---|---|---|
| Classic AJAX | A POST or GET request to the site-specific wp-admin/admin-ajax.php endpoint containing an action value |
A small interaction inside a theme or plugin | WordPress AJAX nonce, plus a capability check for protected work | Usually a compact JSON result tied to one page feature |
| REST API | A request to a registered resource route such as a plugin namespace and version | A durable API, resource-oriented data, block editor integration, or several clients | wp_rest nonce for cookie-authenticated browser requests; Application Passwords for authenticated external REST calls, available since WordPress 5.6 |
Structured resources that are easier to reuse outside the original page |
Classic AJAX is often the shortest path for one feature. REST is usually clearer when the operation represents a resource, must be consumed by more than one screen, or may later serve a mobile app or another client.
Build a classic WordPress AJAX feature
1. Enqueue the script and pass configuration from PHP
Do not hardcode a relative URL in JavaScript. A site may run in a subdirectory, on a different domain, or in a multisite configuration. Generate the endpoint with admin_url( 'admin-ajax.php' ) and expose it with wp_localize_script(). The same server-side step is a convenient place to create the nonce.
#1 Best Overall
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_script(
'my-plugin-ajax',
plugin_dir_url( __FILE__ ) . 'assets/ajax.js',
array( 'jquery' ),
'1.0.0',
true
);
wp_localize_script(
'my-plugin-ajax',
'myAjax',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'my_plugin_action' ),
)
);
} );
The first argument to wp_localize_script() must match the enqueued script handle. WordPress prints an object named myAjax before the JavaScript file, so the browser can read myAjax.ajaxUrl and myAjax.nonce.
For an administration screen, use admin_enqueue_scripts and check the current screen before loading the file. This avoids adding an AJAX script to every dashboard page. WordPress 6.5 and later also support wp_enqueue_script_module() when your implementation is organized as a script module; the endpoint, action, nonce, and server-side checks remain the same.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstall2. Send the request from JavaScript
The request must include an action key. Its value is not a URL and is not automatically derived from the PHP function name: it is the suffix WordPress uses to choose the callback hook.
jQuery.post(
myAjax.ajaxUrl,
{
action: 'my_plugin_action',
_ajax_nonce: myAjax.nonce,
value: document.querySelector('#value').value
}
).done(function (response) {
if (response.success) {
document.querySelector('#result').textContent = response.data.message;
} else {
document.querySelector('#result').textContent = 'The request was rejected.';
}
}).fail(function () {
document.querySelector('#result').textContent = 'The server could not be reached.';
});
jQuery.post() sends form-encoded POST data, which admin-ajax.php accepts. The same protocol works with the browser’s native fetch():
Rank #2
fetch(myAjax.ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
body: new URLSearchParams({
action: 'my_plugin_action',
_ajax_nonce: myAjax.nonce,
value: document.querySelector('#value').value
})
})
.then(function (response) { return response.json(); })
.then(function (response) {
if (response.success) {
document.querySelector('#result').textContent = response.data.message;
}
});
Keep the request method, field names, and response handling consistent with the PHP callback. If you use JSON as the request body, decode and validate it explicitly on the server; form encoding is simpler for the classic endpoint.
3. Register the logged-in and logged-out hooks
WordPress uses two hook families. A logged-in request is dispatched through wp_ajax_<action>. A request from a logged-out visitor is dispatched through wp_ajax_nopriv_<action>. The suffix must exactly match the JavaScript action value.
add_action( 'wp_ajax_my_plugin_action', 'my_plugin_action' );
add_action( 'wp_ajax_nopriv_my_plugin_action', 'my_plugin_action' );
function my_plugin_action() {
check_ajax_referer( 'my_plugin_action' );
if ( ! current_user_can( 'read' ) ) {
wp_send_json_error(
array( 'message' => 'Permission denied.' ),
403
);
}
$value = isset( $_POST['value'] )
? sanitize_text_field( wp_unslash( $_POST['value'] ) )
: '';
wp_send_json_success(
array( 'message' => 'Received: ' . $value )
);
}
Use the nopriv hook only when anonymous visitors are intentionally allowed to perform the operation. In the example, both hooks are registered but the callback still requires the read capability, so an anonymous request fails the authorization check. For a genuinely public operation, choose a policy that does not rely on a logged-in capability and add rate limiting or other abuse controls appropriate to the feature.
Secure every request correctly
Nonce verification
Create the nonce with wp_create_nonce(), send it as _ajax_nonce (or another field accepted by your verification call), and verify it with check_ajax_referer(). The action string used to create and verify it should describe the operation, such as my_plugin_action. A missing, expired, or action-mismatched nonce causes the verification to fail.
A nonce helps defend against cross-site request forgery; it is not a login credential and does not grant permission. Never treat a valid nonce as proof that a visitor may edit content, change settings, delete records, or access private data.
Capability checks
After nonce verification, authorize the requested operation with current_user_can() or an equivalent capability policy. Use the narrowest capability that fits the action. For example, reading a public value, editing a post, managing options, and deleting a user should not all use the same permission. Return an error status such as 403 when the current user is authenticated but not allowed to continue.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Input validation and output
Read only the fields your callback needs. Apply wp_unslash() before sanitizing values from $_POST or $_GET, then validate the resulting type and range. Sanitization is not a substitute for validation: a numeric ID should be checked as an integer, an enumerated setting should be compared with an allow-list, and a URL should be validated for the context in which it will be used.
Use wp_send_json_success() and wp_send_json_error() so the browser receives a predictable object with a Boolean success property and a data member. On the client, handle both branches and the transport failure branch. Do not print PHP notices, template markup, or debugging text into the JSON response.
Rank #4
How WordPress decides whether a request is logged in
The classic endpoint runs in the context of the current WordPress session. A browser carrying a logged-in user’s cookies reaches the wp_ajax_ hook. A visitor without that session reaches the wp_ajax_nopriv_ hook. This is why a callback can appear not to fire even though the action string is correct: only one of the two hooks may have been registered.
- For a member-only operation, register
wp_ajax_<action>and enforce the required capability. - For a public operation, register
wp_ajax_nopriv_<action>and design validation and abuse protection for unauthenticated traffic. - If both audiences are supported, register both hooks and make the callback’s authorization rules explicit rather than assuming the nonce decides access.
Use the REST API when the feature is a resource
A REST route is a better structural fit when the request creates, reads, updates, or deletes a named resource, when several pages need the same endpoint, or when a non-browser client will consume it. Register a namespaced route during rest_api_init and provide a permission_callback; modern WordPress expects that callback to be declared.
add_action( 'rest_api_init', function () {
register_rest_route(
'my-plugin/v1',
'/value',
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'my_plugin_rest_value',
'permission_callback' => function () {
return current_user_can( 'read' );
},
'args' => array(
'value' => array(
'required' => true,
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function ( $value ) {
return is_string( $value ) && $value !== '';
},
),
),
)
);
} );
function my_plugin_rest_value( WP_REST_Request $request ) {
return rest_ensure_response(
array(
'message' => 'Received: ' . $request->get_param( 'value' ),
)
);
}
For a cookie-authenticated browser request, create a nonce with wp_create_nonce( 'wp_rest' ) and send it in the X-WP-Nonce header. The _wpnonce request parameter is also supported. An external authenticated client can use Application Passwords, which WordPress has supported since version 5.6; do not expose those credentials in public browser JavaScript.
fetch('/wp-json/my-plugin/v1/value', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': myRestNonce
},
body: JSON.stringify({ value: 'Example' })
})
.then(function (response) { return response.json(); })
.then(function (data) {
document.querySelector('#result').textContent = data.message;
});
When the route is public, use an intentional public permission_callback and apply validation, pagination, caching, and rate controls appropriate to the data. When it is private, let the permission callback express the policy and return a WP_Error or a false result for unauthorized users.
Best Value
Designing a dependable JSON response
Choose a stable response contract before writing the interface. A successful classic AJAX response from wp_send_json_success() has the shape { success: true, data: ... }; an error response has success: false. Put machine-readable fields inside data, and keep human-facing messages separate from values the client must parse.
- Return an appropriate HTTP status for authorization and validation failures.
- Use one predictable field name for each value; do not make the client infer meaning from a formatted sentence.
- Handle malformed or empty input on the server even if the browser validates it first.
- Make the UI disable duplicate submissions or show a pending state when the operation is not idempotent.
- Log details privately during development, but never mix notices or stack traces into the production response.
Troubleshoot a request that does not work
The callback never runs
- Open the browser’s Network panel and confirm that the request URL is the localized
admin-ajax.phpvalue, not a guessed relative path. - Inspect the submitted fields and verify that
actionis present and spelled exactly like the PHP hook suffix. - Check the browser’s login state. A logged-in request needs
wp_ajax_<action>; a logged-out request needswp_ajax_nopriv_<action>. - Confirm the script was actually enqueued on the page and that the localized object exists before the file executes.
The response says the nonce is invalid
Regenerate the nonce through the enqueue and localization path, then compare the nonce action used by wp_create_nonce() with the action expected by check_ajax_referer(). A cached page can serve stale localized data, and a typo in the submitted field name can leave the callback without the nonce it expects.
Free tools Windows power users keep installed
One-click scans. No signup required.
The request returns an HTML error or an empty body
Look for PHP warnings, fatal errors, or an early die before the JSON function runs. Ensure every execution path ends with a JSON success or error response. In development, inspect the server log rather than echoing diagnostics into the response consumed by JavaScript.
The request works for administrators but not other users
Compare the capability check with the role that is testing the feature. Administrators may pass a broad capability while ordinary users do not. Replace an overly broad permission with the exact capability the operation requires, or revise the feature’s access policy deliberately.
An admin feature loads on the wrong screens
Move the enqueue logic to admin_enqueue_scripts, inspect the supplied screen hook or screen object, and enqueue the script only for the relevant page. This reduces unnecessary requests and prevents unrelated admin pages from receiving feature-specific JavaScript.
Classic AJAX or REST: a practical decision
- Choose classic AJAX for one theme or plugin interaction whose server callback is naturally an action, especially when you need a quick form submission or button response.
- Choose REST when the endpoint represents posts, products, settings, records, or another resource; when multiple screens need it; or when the response should remain useful to other clients.
- Keep security independent of the interface. Both approaches require validation and authorization. A REST nonce and a classic AJAX nonce solve request-integrity concerns, not capability decisions.
- Plan for reuse. A narrowly scoped classic callback can later be replaced by a REST route, but a stable resource model and response schema are easier to extend from the beginning.
Testing and deployment checklist
- Test the feature while logged in as an allowed user, a logged-in user without the required capability, and a logged-out visitor when that audience matters.
- Use the Network panel to verify the URL, method, action, nonce field or header, status code, and response JSON.
- Try missing, empty, malformed, oversized, and unauthorized input; the server must reject it safely.
- Confirm the interface handles success, a structured application error, a 403 response, and a network failure.
- Test on sites installed in a subdirectory and, where relevant, on multisite so hardcoded paths do not slip through.
- For admin functionality, verify that the script is absent from unrelated dashboard screens.
- Remove debugging output and confirm that production responses contain only the documented JSON contract.
The Bottom Line
Use admin-ajax.php with a localized URL, matching action hooks, a verified nonce, capability checks, and structured JSON for small page interactions. Use a REST route when the feature is a reusable resource API. In either case, authorization and server-side validation are mandatory; a nonce alone is never permission.
Recommended Free Tools
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




