Handling AJAX Requests: A Guide to Asynchronous WordPress
Learn to handle AJAX requests in WordPress securely. Master wp_ajax hooks, JSON responses, and frontend communication to power your plugin's interactivity.
Previously in this course, we explored unit testing foundations to ensure our code remains robust under pressure. Now that your plugin logic is verified, it's time to make it dynamic. In this lesson, we are moving beyond traditional page reloads by implementing AJAX (Asynchronous JavaScript and XML) to allow your Knowledge Base plugin to communicate with the server in the background.
Asynchronous Communication from First Principles
In a standard WordPress request, the browser sends a request, the server processes it, and the entire page reloads. AJAX changes this by allowing the frontend to send and receive data without a full refresh.
In WordPress, this is handled via a centralized endpoint: wp-admin/admin-ajax.php. When you send an asynchronous request to this file, WordPress spins up the environment and triggers a specific hook based on the action parameter you provide.
To handle these requests in your plugin, you need three things:
- The Backend Handler: A PHP function hooked into
wp_ajax_{your_action}. - The Security Layer: A nonce to prevent Cross-Site Request Forgery (CSRF).
- The Frontend Trigger: JavaScript using the
fetchAPI to hit the endpoint.
Worked Example: Upvoting a Knowledge Base Article
Let’s add an "Upvote" feature to our Knowledge Base plugin. When a user clicks a button, we want to increment a count in the database without reloading the page.
1. Registering the AJAX Action
In your AdminController or a dedicated AjaxController, register your handler:
PHPadd_action('wp_ajax_kb_upvote_article', [$this, 'handle_upvote']); add_action('wp_ajax_nopriv_kb_upvote_article', [$this, 'handle_upvote']); public function handle_upvote() { #6A9955">// 1. Verify the nonce for security check_ajax_referer('kb_upvote_nonce', 'security'); #6A9955">// 2. Sanitize and validate input $article_id = intval($_POST['id']); #6A9955">// 3. Perform the logic(e.g., updating post meta) $current = get_post_meta($article_id, '_kb_upvote_count', true); update_post_meta($article_id, '_kb_upvote_count', $current + 1); #6A9955">// 4. Send a JSON response and terminate wp_send_json_success(['new_count' => $current + 1]); }
2. Preparing the Frontend
You must pass the AJAX URL and the nonce to your JavaScript file using wp_localize_script, as we covered in enqueuing scripts and styles.
PHPwp_localize_script('kb-frontend-js', 'kb_ajax', [ 'url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('kb_upvote_nonce') ]);
3. The JavaScript Request
Now, use fetch to send the request. If you are building modern interfaces, remember that handling form submissions or fetching data from an API follows similar asynchronous patterns.
JAVASCRIPTconst upvoteArticle = async (articleId) => { const formData = new FormData(); formData.append(CE9178">'action', CE9178">'kb_upvote_article'); formData.append(CE9178">'security', kb_ajax.nonce); formData.append(CE9178">'id', articleId); const response = await fetch(kb_ajax.url, { method: CE9178">'POST', body: formData }); const data = await response.json(); if (data.success) { console.log(CE9178">'New count:', data.data.new_count); } };
Hands-on Exercise
- Create a new button in your Knowledge Base single-article template.
- Localize a script that passes the current post ID and a generated nonce to your JS.
- Implement the
wp_ajax_hook in your plugin core to handle the ID increment and return a JSON object withwp_send_json_success(). - Use
fetchto send the request and update the button text with the new count returned from the server.
Common Pitfalls
- Forgetting
wp_die()orwp_send_json(): If you don't terminate the script, WordPress appends a0(or the whole page HTML) to your JSON response, causing a parsing error.wp_send_json_successhandles this for you. - Missing
wp_ajax_nopriv_: If you want logged-out users to use your AJAX features, you must hook into bothwp_ajax_{action}andwp_ajax_nopriv_{action}. - Hardcoding URLs: Never hardcode
admin-ajax.php. Always useadmin_url('admin-ajax.php')to ensure compatibility with SSL and multisite environments. - Security Neglect: Always use
check_ajax_refererto verify that the request originated from your own site's form.
Recap
We’ve learned that AJAX is the backbone of modern, interactive WordPress plugins. By using wp_ajax_ hooks, we can keep our PHP logic encapsulated while providing a fluid user experience. Always remember to sanitize your inputs, verify your nonces, and output valid JSON. These practices ensure your plugin remains secure and professional as it grows.
Up next: We will move from the legacy admin-ajax system to the modern, clean approach of REST API Integration.
Work with me

Custom WordPress Plugin Development
Custom WordPress & WooCommerce plugins built to standard — by the developer behind a plugin with 5,000+ active installs and a SaaS with 10,000+ users.

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.