{"slug":"wordpress-ajax-handlers","title":"WordPress AJAX Handlers","tags":["development","ajax","plugin","security"],"agent_summary":"Pattern for registering WordPress AJAX actions with nonce verification for both logged-in and public users.","trigger_phrases":["wordpress ajax","wp_ajax","ajax handler wordpress","wordpress form submission ajax","wp_send_json"],"runnable":true,"markdown":"\nWordPress AJAX uses `admin-ajax.php` as the endpoint. Always verify a nonce — never process requests without it.\n\n## Enqueue Script with AJAX Data\n\n```php\nfunction my_plugin_enqueue_scripts() {\n    wp_enqueue_script(\n        'my-plugin-frontend',\n        plugin_dir_url(__FILE__) . 'assets/js/frontend.js',\n        ['jquery'],\n        '1.0.0',\n        true // load in footer\n    );\n\n    // Pass AJAX URL and nonce to JavaScript\n    wp_localize_script('my-plugin-frontend', 'myPlugin', [\n        'ajaxUrl' => admin_url('admin-ajax.php'),\n        'nonce'   => wp_create_nonce('my_plugin_action'),\n    ]);\n}\nadd_action('wp_enqueue_scripts', 'my_plugin_enqueue_scripts');\n```\n\n## Register AJAX Actions\n\n```php\n// For logged-in users\nadd_action('wp_ajax_my_plugin_action', 'my_plugin_ajax_handler');\n\n// For non-logged-in (public) users — add both for public forms\nadd_action('wp_ajax_nopriv_my_plugin_action', 'my_plugin_ajax_handler');\n\nfunction my_plugin_ajax_handler() {\n    // 1. Verify nonce\n    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'my_plugin_action')) {\n        wp_send_json_error(['message' => 'Security check failed'], 403);\n    }\n\n    // 2. Sanitize input\n    $name    = sanitize_text_field($_POST['name'] ?? '');\n    $email   = sanitize_email($_POST['email'] ?? '');\n    $message = sanitize_textarea_field($_POST['message'] ?? '');\n\n    // 3. Validate\n    if (empty($name) || empty($email) || !is_email($email)) {\n        wp_send_json_error(['message' => 'Invalid input']);\n    }\n\n    // 4. Process (save to DB, send email, etc.)\n    // ...\n\n    // 5. Return success\n    wp_send_json_success(['message' => 'Submission received']);\n}\n```\n\n## JavaScript (jQuery) Request\n\n```javascript\njQuery(function($) {\n    $('#my-form').on('submit', function(e) {\n        e.preventDefault();\n\n        $.ajax({\n            url: myPlugin.ajaxUrl,\n            type: 'POST',\n            data: {\n                action: 'my_plugin_action',\n                nonce: myPlugin.nonce,\n                name: $('#name').val(),\n                email: $('#email').val(),\n                message: $('#message').val(),\n            },\n            success: function(response) {\n                if (response.success) {\n                    alert(response.data.message);\n                } else {\n                    alert('Error: ' + response.data.message);\n                }\n            },\n            error: function() {\n                alert('Request failed. Try again.');\n            }\n        });\n    });\n});\n```\n\n## Sanitization Quick Reference\n\n| Input Type | Function |\n|------------|---------|\n| Plain text | `sanitize_text_field()` |\n| Email | `sanitize_email()` |\n| URL | `esc_url_raw()` |\n| Integer | `absint()` or `intval()` |\n| HTML content | `wp_kses_post()` |\n| Multi-line text | `sanitize_textarea_field()` |\n\nAlways sanitize on input, escape on output (`esc_html()`, `esc_attr()`, `esc_url()`).\n","html":"<p>WordPress AJAX uses <code>admin-ajax.php</code> as the endpoint. Always verify a nonce — never process requests without it.</p>\n<h2>Enqueue Script with AJAX Data</h2>\n<pre><code class=\"language-php\">function my_plugin_enqueue_scripts() {\n    wp_enqueue_script(\n        'my-plugin-frontend',\n        plugin_dir_url(__FILE__) . 'assets/js/frontend.js',\n        ['jquery'],\n        '1.0.0',\n        true // load in footer\n    );\n\n    // Pass AJAX URL and nonce to JavaScript\n    wp_localize_script('my-plugin-frontend', 'myPlugin', [\n        'ajaxUrl' => admin_url('admin-ajax.php'),\n        'nonce'   => wp_create_nonce('my_plugin_action'),\n    ]);\n}\nadd_action('wp_enqueue_scripts', 'my_plugin_enqueue_scripts');\n</code></pre>\n<h2>Register AJAX Actions</h2>\n<pre><code class=\"language-php\">// For logged-in users\nadd_action('wp_ajax_my_plugin_action', 'my_plugin_ajax_handler');\n\n// For non-logged-in (public) users — add both for public forms\nadd_action('wp_ajax_nopriv_my_plugin_action', 'my_plugin_ajax_handler');\n\nfunction my_plugin_ajax_handler() {\n    // 1. Verify nonce\n    if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'my_plugin_action')) {\n        wp_send_json_error(['message' => 'Security check failed'], 403);\n    }\n\n    // 2. Sanitize input\n    $name    = sanitize_text_field($_POST['name'] ?? '');\n    $email   = sanitize_email($_POST['email'] ?? '');\n    $message = sanitize_textarea_field($_POST['message'] ?? '');\n\n    // 3. Validate\n    if (empty($name) || empty($email) || !is_email($email)) {\n        wp_send_json_error(['message' => 'Invalid input']);\n    }\n\n    // 4. Process (save to DB, send email, etc.)\n    // ...\n\n    // 5. Return success\n    wp_send_json_success(['message' => 'Submission received']);\n}\n</code></pre>\n<h2>JavaScript (jQuery) Request</h2>\n<pre><code class=\"language-javascript\">jQuery(function($) {\n    $('#my-form').on('submit', function(e) {\n        e.preventDefault();\n\n        $.ajax({\n            url: myPlugin.ajaxUrl,\n            type: 'POST',\n            data: {\n                action: 'my_plugin_action',\n                nonce: myPlugin.nonce,\n                name: $('#name').val(),\n                email: $('#email').val(),\n                message: $('#message').val(),\n            },\n            success: function(response) {\n                if (response.success) {\n                    alert(response.data.message);\n                } else {\n                    alert('Error: ' + response.data.message);\n                }\n            },\n            error: function() {\n                alert('Request failed. Try again.');\n            }\n        });\n    });\n});\n</code></pre>\n<h2>Sanitization Quick Reference</h2>\n<p>| Input Type | Function |\n|------------|---------|\n| Plain text | <code>sanitize_text_field()</code> |\n| Email | <code>sanitize_email()</code> |\n| URL | <code>esc_url_raw()</code> |\n| Integer | <code>absint()</code> or <code>intval()</code> |\n| HTML content | <code>wp_kses_post()</code> |\n| Multi-line text | <code>sanitize_textarea_field()</code> |</p>\n<p>Always sanitize on input, escape on output (<code>esc_html()</code>, <code>esc_attr()</code>, <code>esc_url()</code>).</p>\n"}