Skip to content

AJAX Request

The AJAX_Request class provides a declarative way to register WordPress AJAX handlers. It handles nonce verification, capability checks, input sanitization, and standardized JSON responses automatically — so you can focus on the business logic of your callback.


  • Automatic Nonce Verification: Generates and verifies nonces per action name
  • Built-in Sanitization: 14+ sanitizer types (text, email, int, html, array_int, etc.)
  • Required Fields: Validates presence of required fields before calling your callback
  • Capability Checks: Optionally restrict to users with a specific WordPress capability
  • Standardized Responses: send_success(), send_error(), and send_paginated() helpers
  • Public/Private: Control whether non-logged-in users can access the endpoint

  • action (string, required): The AJAX action name. Becomes the wp_ajax_{action} hook.
  • callback (callable, required): The function to execute. Receives ($data, $ajax) — where $data is the pre-sanitized input array.
  • public (bool): Allow non-logged-in users. Default: false.
  • verify_nonce (bool): Verify nonce automatically. Default: true.
  • nonce_name (string): Custom nonce field name. Default: 'nonce'.
  • capability (string): Required WordPress capability (e.g., edit_posts). Default: null.
  • method (string): HTTP method: 'POST', 'GET', or 'ANY'. Default: 'POST'.
  • required (array): Fields that must be present in the request.
  • sanitize (array): Sanitization rules per field (see table below).

TypeAliasesWordPress Function
textstringsanitize_text_field()
textareasanitize_textarea_field()
intintegerintval()
floatnumberfloatval()
boolbooleanfilter_var(FILTER_VALIDATE_BOOLEAN)
emailsanitize_email()
urlesc_url_raw()
htmlwp_kses_post()
keyslugsanitize_key()
filenamefilesanitize_file_name()
array_intintval() on each element
array_textsanitize_text_field() on each element
rawnoneNo sanitization
callableYour own custom function

A simple public AJAX handler for a contact form:

<?php
// In functions/project/config/ajax_config.php
return [
[
'action' => 'submit_contact',
'public' => true,
'verify_nonce' => true,
'method' => 'POST',
'required' => ['email', 'message'],
'sanitize' => [
'name' => 'text',
'email' => 'email',
'message' => 'textarea',
],
'callback' => function ($data, $ajax) {
// $data is already sanitized
$sent = wp_mail('info@example.com', 'Contact', $data['message']);
if ($sent) {
AJAX_Request::send_success(
['email' => $data['email']],
'Message sent!'
);
} else {
AJAX_Request::send_error('mail_failed', 'Could not send.', 500);
}
},
],
];

For paginated content loading (e.g., “Load More” buttons):

<?php
[
'action' => 'loadmore_posts',
'public' => true,
'verify_nonce' => true,
'sanitize' => [
'page' => 'int',
'per_page' => 'int',
'post_type' => 'text',
'template' => 'text',
],
'callback' => function ($data) {
$query = new WP_Query([
'post_type' => $data['post_type'] ?: 'post',
'posts_per_page' => $data['per_page'] ?: 6,
'paged' => $data['page'] ?: 1,
]);
ob_start();
while ($query->have_posts()) {
$query->the_post();
include(locate_template('components/card/' . $data['template'] . '.php'));
}
wp_reset_postdata();
$html = ob_get_clean();
AJAX_Request::send_paginated(
$html,
$data['page'] < $query->max_num_pages,
(int) $data['page'],
$query->found_posts
);
},
],

Restrict the handler to logged-in users with the edit_posts capability:

<?php
[
'action' => 'admin_update_status',
'public' => false,
'capability' => 'edit_posts',
'required' => ['post_id', 'status'],
'sanitize' => [
'post_id' => 'int',
'status' => 'text',
],
'callback' => function ($data) {
wp_update_post([
'ID' => $data['post_id'],
'post_status' => $data['status'],
]);
AJAX_Request::send_success([], 'Status updated.');
},
],

Use a closure for complex sanitization logic:

<?php
'sanitize' => [
'ids' => function ($value) {
if (!is_array($value)) return [];
return array_filter(array_map('absint', $value));
},
],

Use these static methods inside your callback:

<?php
// Success (HTTP 200)
AJAX_Request::send_success(['key' => 'value'], 'It worked!');
// Error (custom HTTP status)
AJAX_Request::send_error('error_code', 'Something went wrong.', 400);
// Paginated (for load-more patterns)
AJAX_Request::send_paginated($html, $has_more, $current_page, $total);

The nonce for each action is available via terra_ajax_nonce() in PHP. Pass it in your form data:

const formData = new FormData();
formData.append('action', 'submit_contact');
formData.append('nonce', window.base_wp_api.nonces.submit_contact);
formData.append('email', 'user@example.com');
formData.append('message', 'Hello!');
fetch(window.base_wp_api.ajax_url, {
method: 'POST',
body: formData,
})
.then(res => res.json())
.then(json => {
if (json.success) {
console.log(json.message);
} else {
console.error(json.error.message);
}
});

Knowledge Check

Test your understanding of this section

Loading questions...