WordPress AJAX: обработчик, nonce и JSON-ответ
Как сделать AJAX в WordPress через admin-ajax.php: подключение JS, action, nonce, check_ajax_referer(), sanitization и JSON-ответы.

Короткий ответ: AJAX в WordPress делается через обработчик на хуках wp_ajax_* и wp_ajax_nopriv_*, передачу admin-ajax.php и nonce в JavaScript, проверку check_ajax_referer() на сервере, sanitization входящих данных и ответ через wp_send_json_success() или wp_send_json_error().
Если сказать проще: JavaScript отправляет запрос без перезагрузки страницы, WordPress на сервере принимает этот запрос, проверяет безопасность и возвращает ответ в формате JSON. JSON — это удобный формат данных для JavaScript, например { "success": true }.
Ниже — рабочий пример для публичной формы, которую можно добавить в дочернюю тему или небольшой кастомный плагин. Он показывает полный цикл: подключение JS, передача настроек, отправка fetch(), проверка nonce, обработка данных и единый JSON-ответ.

Что должно получиться
В конце у вас будет простая форма: посетитель вводит имя и сообщение, нажимает кнопку, страница не перезагружается, а под формой появляется результат. На сервере WordPress проверит nonce, очистит входящие данные и вернет понятный JSON-ответ.
Nonce — это одноразовый защитный токен WordPress. Он не заменяет проверку прав, но помогает убедиться, что запрос пришел из ожидаемого места, а не с чужой страницы. Action — это имя действия, по которому WordPress понимает, какой PHP-обработчик запускать.
Когда использовать admin-ajax.php
admin-ajax.php подходит для небольших интерактивных задач на сайте: отправить форму без перезагрузки, подгрузить карточки, проверить промокод, сохранить настройку пользователя, обновить счетчик или вернуть HTML-фрагмент.
Если вы строите полноценный API для внешних клиентов, сложных фильтров или мобильного приложения, часто лучше использовать REST API. Но для доработок темы и привычной WordPress-интеграции AJAX через admin-ajax.php остается понятным и надежным вариантом.
Для начинающего разработчика удобнее начать именно с admin-ajax.php, потому что этот механизм уже встроен в WordPress. Не нужно создавать отдельный файл-обработчик, подключать WordPress вручную или придумывать свой роутинг.
Как устроен AJAX в WordPress
Каждый запрос должен передавать параметр action. По нему WordPress выбирает PHP-хук:
wp_ajax_ink_send_request— для авторизованных пользователей;wp_ajax_nopriv_ink_send_request— для посетителей без входа в админку.
Если форма доступна всем посетителям, регистрируйте оба хука. Если действие должно быть доступно только редактору, администратору или владельцу данных, используйте только wp_ajax_* и дополнительно проверяйте права через current_user_can().
Главное совпадение: если в JavaScript отправляется action=ink_send_request, то в PHP должны быть хуки wp_ajax_ink_send_request и, если форма публичная, wp_ajax_nopriv_ink_send_request. Ошибка в одной букве приведет к тому, что WordPress не найдет обработчик.
Подключение JavaScript и передача ajaxUrl
Не прописывайте адрес /wp-admin/admin-ajax.php вручную в JS-файле. В WordPress корректнее подключить скрипт через wp_enqueue_script() и передать параметры через wp_localize_script() или wp_add_inline_script(). Базовый подход к подключению файлов разобран отдельно в статье как подключить CSS и JavaScript через wp_enqueue.
Куда вставить: этот PHP-код добавьте в functions.php дочерней темы или в небольшой кастомный плагин. JS-файл должен лежать по пути assets/js/request-form.js внутри дочерней темы, если вы используете пример без изменений.
<?php
/**
* Подключает JS формы и передает параметры для AJAX.
*/
function ink_enqueue_request_form_assets() {
wp_enqueue_script(
'ink-request-form',
get_stylesheet_directory_uri() . '/assets/js/request-form.js',
array(),
'1.0.0',
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
wp_localize_script(
'ink-request-form',
'InkRequestForm',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'ink_request_form' ),
)
);
}
add_action( 'wp_enqueue_scripts', 'ink_enqueue_request_form_assets' );
Название объекта InkRequestForm должно быть уникальным для проекта, чтобы не пересечься с другими скриптами темы или плагинов.
Разбор кода: wp_enqueue_script() подключает файл JavaScript правильным WordPress-способом. admin_url( 'admin-ajax.php' ) дает точный URL обработчика. wp_create_nonce() создает защитный токен. wp_localize_script() передает эти значения в JS как объект window.InkRequestForm.
HTML формы
Форма может находиться в шаблоне страницы, shortcode или template part. Для примера оставим два поля: имя и сообщение.
Куда вставить: в шаблон страницы, template part или shortcode, где должна отображаться форма. Для первого теста лучше поставить форму на отдельную тестовую страницу.
<form class="ink-request-form" data-ink-request-form>
<label>
Ваше имя
<input type="text" name="name" autocomplete="name" required>
</label>
<label>
Сообщение
<textarea name="message" rows="4" required></textarea>
</label>
<button type="submit">Отправить</button>
<p class="ink-request-form__status" aria-live="polite"></p>
</form>
Для реальной формы добавьте нормальные стили, состояния loading, success и error. Если задача связана с отправкой заявки, не храните чувствительные данные в открытых data-атрибутах.
Разбор HTML: атрибут data-ink-request-form нужен, чтобы JavaScript нашел именно эту форму. Поле aria-live="polite" помогает озвучивать статус отправки для пользователей экранных дикторов. Кнопку мы потом временно отключим на время запроса, чтобы форму не отправили дважды.
JavaScript на fetch
В файле assets/js/request-form.js найдите форму, соберите FormData, добавьте action и nonce, затем обработайте JSON-ответ.
Куда вставить: в файл assets/js/request-form.js, который подключается через wp_enqueue_script() в предыдущем блоке.
( function () {
const form = document.querySelector( '[data-ink-request-form]' );
if ( ! form || ! window.InkRequestForm ) {
return;
}
const status = form.querySelector( '.ink-request-form__status' );
const button = form.querySelector( 'button[type="submit"]' );
form.addEventListener( 'submit', async function ( event ) {
event.preventDefault();
const formData = new FormData( form );
formData.append( 'action', 'ink_send_request' );
formData.append( '_ajax_nonce', window.InkRequestForm.nonce );
if ( button ) {
button.disabled = true;
}
if ( status ) {
status.textContent = 'Отправляем...';
}
try {
const response = await fetch( window.InkRequestForm.ajaxUrl, {
method: 'POST',
credentials: 'same-origin',
body: formData,
} );
const result = await response.json();
if ( ! response.ok || ! result.success ) {
throw new Error(
result.data && result.data.message
? result.data.message
: 'Не удалось отправить форму.'
);
}
form.reset();
if ( status ) {
status.textContent = result.data.message;
}
} catch ( error ) {
if ( status ) {
status.textContent = error.message;
}
} finally {
if ( button ) {
button.disabled = false;
}
}
} );
}() );
credentials: 'same-origin' оставляет запрос в рамках текущего сайта и позволяет WordPress работать с cookie авторизованного пользователя, когда это нужно.
Разбор JavaScript: event.preventDefault() останавливает обычную отправку формы с перезагрузкой страницы. FormData собирает поля формы. action говорит WordPress, какой PHP-хук искать. _ajax_nonce передает nonce на сервер. response.json() превращает ответ сервера в объект, с которым удобно работать в JS.
PHP обработчик с nonce и sanitization
Теперь добавим серверный обработчик. Его можно разместить в functions.php дочерней темы или в кастомном плагине. Для постоянной бизнес-логики лучше небольшой плагин: он не зависит от смены темы.
Куда вставить: в тот же functions.php дочерней темы или в кастомный плагин, где вы подключали JS. Важно, чтобы имя action в JavaScript совпадало с именем в PHP-хуках.
<?php
/**
* Обрабатывает публичную AJAX-форму.
*/
function ink_handle_request_form() {
check_ajax_referer( 'ink_request_form' );
$name = isset( $_POST['name'] )
? sanitize_text_field( wp_unslash( $_POST['name'] ) )
: '';
$message = isset( $_POST['message'] )
? sanitize_textarea_field( wp_unslash( $_POST['message'] ) )
: '';
if ( '' === $name || '' === $message ) {
wp_send_json_error(
array( 'message' => 'Заполните имя и сообщение.' ),
400
);
}
// Здесь может быть отправка письма, создание заявки или своя бизнес-логика.
wp_send_json_success(
array( 'message' => 'Сообщение отправлено.' ),
200
);
}
add_action( 'wp_ajax_ink_send_request', 'ink_handle_request_form' );
add_action( 'wp_ajax_nopriv_ink_send_request', 'ink_handle_request_form' );
check_ajax_referer() проверяет nonce и по умолчанию прекращает выполнение при ошибке. Входящие значения проходят через wp_unslash() и sanitization. Ответ возвращается через wp_send_json_success() или wp_send_json_error(), поэтому вручную вызывать wp_die() после них не нужно.
Разбор PHP: check_ajax_referer( 'ink_request_form' ) должен использовать ту же строку, что и wp_create_nonce( 'ink_request_form' ). wp_unslash() убирает служебное экранирование WordPress из входящих данных. sanitize_text_field() и sanitize_textarea_field() очищают текст. wp_send_json_success() сразу отправляет JSON и завершает выполнение.
Проверка прав для закрытых действий
Nonce не является проверкой доступа. Он помогает защититься от CSRF, но не доказывает, что пользователь имеет право выполнять действие. Для операций в админке, изменения записей, удаления данных и работы с личной информацией добавляйте current_user_can().
Куда вставить: этот пример нужен только для закрытых действий в админке или личном кабинете. Для публичной формы заявки он не обязателен, но принцип проверки прав важно запомнить.
<?php
function ink_save_private_note() {
check_ajax_referer( 'ink_private_note' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error(
array( 'message' => 'Недостаточно прав.' ),
403
);
}
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
$note = isset( $_POST['note'] )
? sanitize_textarea_field( wp_unslash( $_POST['note'] ) )
: '';
if ( ! $post_id || '' === $note ) {
wp_send_json_error(
array( 'message' => 'Некорректные данные.' ),
400
);
}
update_post_meta( $post_id, '_ink_private_note', $note );
wp_send_json_success(
array( 'message' => 'Заметка сохранена.' ),
200
);
}
add_action( 'wp_ajax_ink_save_private_note', 'ink_save_private_note' );
Для закрытого действия не нужен wp_ajax_nopriv_*. Если зарегистрировать его для гостей, вы расширите поверхность атаки и усложните проверку.
Разбор проверки прав: nonce отвечает на вопрос “запрос выглядит как запрос с нашего сайта?”. current_user_can() отвечает на другой вопрос: “этому пользователю вообще можно выполнять действие?”. Для сохранения записей, настроек и личных данных нужны обе проверки.
Какие HTTP-коды возвращать
У wp_send_json_success() и wp_send_json_error() можно передать HTTP-код. Это полезно для JavaScript: response.ok будет отражать реальное состояние ответа.
200— запрос выполнен успешно;400— не хватает данных или формат неверный;403— nonce не прошел или у пользователя нет прав;500— внутренняя ошибка, которую нельзя показать как ошибку пользователя.
Не возвращайте все ошибки с кодом 200. Тогда фронтенд будет считать неудачный запрос успешным и вам придется дублировать логику обработки.
Если не работает
Забыли передать action
Без action WordPress не найдет обработчик. Название в JS должно совпадать с частью хука после wp_ajax_ или wp_ajax_nopriv_. В DevTools откройте Network, выберите запрос к admin-ajax.php и проверьте, есть ли в payload строка action.
Nonce создан с одним именем, а проверяется с другим
Строка в wp_create_nonce( 'ink_request_form' ) должна совпадать со строкой в check_ajax_referer( 'ink_request_form' ). Если используете свое имя поля вместо _ajax_nonce, укажите его вторым параметром проверки. Для теста временно выведите объект window.InkRequestForm в консоль и убедитесь, что nonce вообще передался в JS.
Нет обработки для гостей
Если форма доступна неавторизованным посетителям, добавьте wp_ajax_nopriv_*. Если действие только для админки, наоборот, не добавляйте nopriv-хук.
Данные сразу пишутся в базу
Перед сохранением проверяйте nonce, права, обязательные поля и типы данных. Для ID используйте absint(), для текста — sanitize_text_field() или sanitize_textarea_field(), для URL — esc_url_raw() при сохранении и esc_url() при выводе.
В ответе приходит 0
Чаще всего это значит, что WordPress не нашел нужный action или обработчик завершился не так, как ожидается. Проверьте совпадение ink_send_request в JS и PHP, наличие add_action(), а также PHP-ошибки в логах.
Как проверить AJAX-обработчик
- Откройте страницу с формой без авторизации и проверьте, что JS-файл загружен со статусом 200.
- В DevTools на вкладке Network отправьте форму и найдите запрос к
admin-ajax.php. - Проверьте payload: есть
action,_ajax_nonceи нужные поля. - Проверьте JSON-ответ: есть
successиdata.message. - Отправьте пустую форму и убедитесь, что сервер возвращает 400.
- Временно передайте неверный nonce и убедитесь, что запрос не выполняет действие.
Если нужно добавить AJAX-фильтр, калькулятор, форму или личный кабинет в существующую тему WordPress, можно начать с безопасного обработчика и отдельного тестового сценария. Для такой работы подойдет заявка на техническую поддержку WordPress.
Итог
Надежный AJAX в WordPress держится на пяти вещах: правильный enqueue, передача ajaxUrl и nonce в JS, обработчик на нужном wp_ajax_* хуке, sanitization входящих данных и JSON-ответ с корректным HTTP-кодом. Nonce не заменяет проверку прав, а wp_send_json_success() и wp_send_json_error() помогают держать единый формат ответа для фронтенда.
Официальная документация: AJAX в WordPress Plugin Handbook, WordPress Nonces, wp_send_json_success(), wp_send_json_error(), wp_ajax_nopriv_*.

