Кратко: AJAX в WordPress — это способ обмениваться данными с сервером без перезагрузки страницы. Скрипт в браузере отправляет запрос на файл admin-ajax.php с параметром action, WordPress находит обработчик по хукам wp_ajax_{action} (для авторизованных) и wp_ajax_nopriv_{action} (для гостей), а PHP-функция возвращает ответ в JSON через wp_send_json_success() или wp_send_json_error(). Для защиты запросов используется nonce (wp_create_nonce + check_ajax_referer), а адрес admin-ajax.php и ключ передаются в скрипт через wp_localize_script(). Ниже — полный разбор с готовым кодом: кнопка «Полезно?», лента «Показать ещё» на WP_Query, вариант без jQuery на fetch, сравнение admin-ajax и REST API в таблице и частые ошибки новичков.
Нажимаете «Показать ещё» — и новые статьи появляются под текущими без мигания страницы. Меняете фильтр в каталоге — список товаров обновляется за секунду. Отправляете форму — и видите «Сообщение отправлено» прямо на месте формы. Всё это делает AJAX: технология, которая позволяет странице тихо общаться с сервером в фоне. В WordPress для этого есть готовый механизм — файл admin-ajax.php, и разобраться в нём проще, чем кажется.
Материал рассчитан на новичков: каждый шаг объясняется простыми словами, весь код можно скопировать и вставить в functions.php. Лучше всего делать это в дочерней теме WordPress — так правки не пропадут при обновлении основной темы. А если вы уже читали статью про хуки WordPress: actions и filters, будет вдвойне проще — AJAX-обработчики подключаются точно так же, через add_action.
Что такое AJAX простыми словами и зачем он сайту
AJAX расшифровывается как Asynchronous JavaScript And XML — «асинхронный JavaScript и XML». Звучит заумно, но идея умещается в одну фразу: обычная страница работает по схеме «запрос — полная перезагрузка — новая страница», а AJAX отправляет запрос к серверу из JavaScript, получает маленький кусочек данных и обновляет только нужную часть страницы — счётчик, список, сообщение.
XML сегодня почти не используется — данные ходят в формате JSON, но название прижилось. Когда слышите «AJAX-запрос», просто думайте: «JavaScript спросил у сервера что-то и получил ответ, при этом страница не перезагружалась».
Вы уже пользуетесь AJAX на каждом втором сайте, даже если не задумывались: живой поиск, отправка комментариев без перехода, обновление корзины WooCommerce при смене количества товара, бесконечные ленты, лайки и рейтинги, динамические фильтры каталогов. Плюсы технологии очевидны: скорость (не гоняется вся страница), экономия трафика и приятное впечатление посетителей. Минус один, но важный: каждый AJAX-запрос нагружает сервер так же, как заход посетителя, поэтому обработчики нужно защищать и не оставлять «дырявыми» — об этом отдельный раздел ниже.
Как устроен admin-ajax.php: путь запроса от браузера до PHP-функции
В каждой установке WordPress есть файл /wp-admin/admin-ajax.php — единая точка входа для всех AJAX-запросов. Доступ к нему открыт и авторизованным пользователям, и гостям. Вот что происходит при запросе:
- Скрипт в браузере отправляет POST- или GET-запрос на admin-ajax.php и передаёт параметр
action— например,action=dscw_like; - WordPress загружает ядро, читает action и строит два имени хука:
wp_ajax_dscw_like(если запрос от авторизованного) иwp_ajax_nopriv_dscw_like(если от гостя); - Выполняются все PHP-функции, повешенные на нужный хук через
add_action; - Функция формирует ответ (обычно в JSON) и завершает выполнение;
- JavaScript получает ответ и обновляет часть страницы.
Из этого следуют два важных симптома, которые вы почти наверняка встретите. Первое: если обработчик с таким именем не зарегистрирован, admin-ajax.php отвечает 0 — это почти всегда опечатка в имени action или забытый хук wp_ajax_nopriv_*. Второе: если запрос ушёл вообще без параметра action, вернётся ошибка 400 Bad Request.
Ещё один нюанс, о котором стоит знать заранее: admin-ajax.php при каждом запросе загружает весь WordPress, включая часть админки. Для десятков запросов это незаметно, но на посещаемом сайте тысячи таких вызовов создают нагрузку. Для тяжёлых API команда WordPress развивает REST API WordPress — отдельный механизм с маршрутизацией и облегчённой загрузкой ядра. Для мелких интерактивов в теме admin-ajax по-прежнему самый простой путь для новичка, а сравнение обоих подходов мы сведём в таблицу в разделе 7.
Первый AJAX-запрос: кнопка «Полезно?» со счётчиком лайков
Сделаем классическую задачу новичка: под записью стоит кнопка «Полезно?», при клике счётчик увеличивается на единицу, страница не перезагружается. Три шага: HTML кнопки, PHP-обработчик, JavaScript.
Шаг 1. Кнопка в шаблоне записи (например, в single.php вашей темы):
<button class="dscw-like-btn" data-post-id="<?php echo get_the_ID(); ?>">
Полезно? <span class="dscw-likes-count"><?php echo (int) get_post_meta( get_the_ID(), 'dscw_likes', true ); ?></span>
</button> Шаг 2. PHP-код в functions.php: подключаем скрипт, передаём в него адрес и ключ защиты через wp_localize_script, регистрируем два хука и пишем обработчик:
// 1. Подключаем скрипт и передаём в него адрес admin-ajax.php и nonce
add_action( 'wp_enqueue_scripts', 'dscw_like_assets' );
function dscw_like_assets() {
wp_enqueue_script(
'dscw-likes',
get_stylesheet_directory_uri() . '/js/dscw-likes.js',
array( 'jquery' ),
'1.0',
true
);
wp_localize_script( 'dscw-likes', 'dscwAjax', array(
'url' => admin_url( 'admin-ajax.php' ), // куда отправлять запросы
'nonce' => wp_create_nonce( 'dscw_likes' ), // ключ защиты
) );
}
// 2. Обработчик для авторизованных пользователей
add_action( 'wp_ajax_dscw_like', 'dscw_like_handler' );
// 3. Обработчик для гостей (неавторизованных)
add_action( 'wp_ajax_nopriv_dscw_like', 'dscw_like_handler' );
function dscw_like_handler() {
// Проверяем nonce — защита от подделки запросов
check_ajax_referer( 'dscw_likes', 'nonce' );
$post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0;
if ( ! $post_id || ! get_post( $post_id ) ) {
wp_send_json_error( 'Запись не найдена' );
}
// Считаем лайки в метаполе записи
$likes = (int) get_post_meta( $post_id, 'dscw_likes', true );
$likes++;
update_post_meta( $post_id, 'dscw_likes', $likes );
wp_send_json_success( array(
'likes' => $likes,
'message' => 'Спасибо за оценку!',
) );
} Разберём ключевые строки, потому что здесь собран весь «джентльменский набор» темы:
wp_enqueue_scripts— стандартный способ подключать скрипты и стили. Мы кладём файл dscw-likes.js в папку js активной (дочерней) темы и объявляем зависимость от jQuery;wp_localize_script— мост между PHP и JavaScript: превращает PHP-массив в глобальный JS-объект. Теперь в скрипте доступныdscwAjax.url(адрес admin-ajax.php) иdscwAjax.nonce(ключ защиты);wp_ajax_dscw_likeиwp_ajax_nopriv_dscw_like— два хука: первый срабатывает для авторизованных, второй для гостей. Забыли второй — гости получат в ответ «0»;wp_send_json_success()иwp_send_json_error()возвращают JSON вида{"success":true,"data":{...}}и сами завершают выполнение —die()в конце не нужен.
Шаг 3. JavaScript в файле js/dscw-likes.js:
jQuery(function ($) {
$(document).on('click', '.dscw-like-btn', function (e) {
e.preventDefault();
var $btn = $(this);
$.post(dscwAjax.url, {
action: 'dscw_like', // WordPress ищет хук wp_ajax_dscw_like
nonce: dscwAjax.nonce,
post_id: $btn.data('post-id')
})
.done(function (response) {
if (response.success) {
$btn.find('.dscw-likes-count').text(response.data.likes);
} else {
alert(response.data);
}
})
.fail(function () {
alert('Сервер недоступен. Попробуйте позже.');
});
});
}); Обратите внимание: action: 'dscw_like' — именно это значение WordPress подставит в имя хука. Ответ сервера приходит в свойстве data: response.data.likes — свежее значение счётчика, которое мы выводим вместо старого. Кнопка ожила: клик уходит на сервер, счётчик обновляется, страница стоит на месте.
Nonce и безопасность: четыре правила, которые нельзя нарушать
AJAX-обработчик — это дверь в ваш сервер, открытая для любого посетителя. Nonce (number used once) — ключ, который доказывает: запрос отправлен со страницы вашего сайта, а не подделан злоумышленником. Он генерируется в PHP через wp_create_nonce('dscw_likes'), живёт 12–24 часа, а проверяется в обработчике через check_ajax_referer('dscw_likes', 'nonce'): если ключа в запросе нет или он не совпал, скрипт завершится с ответом «-1».
Четыре обязательных правила для каждого обработчика:
- Nonce — всегда. Проверяйте
check_ajax_referer()даже в обработчиках для гостей: без него любой сайт сможет слать запросы от имени ваших посетителей; - Валидация входных данных. Числа — через
absint(), строки — черезsanitize_text_field(). Считайте, что данных из$_POSTне существует, пока вы их не прогнали через эти функции; - Права доступа. Для действий, доступных только администраторам (сохранение настроек, правка контента), используйте
current_user_can('manage_options')и регистрируйте хук только наwp_ajax_*— без nopriv; - Экранирование вывода. Всё, что попадает из базы на страницу, прогоняйте через
esc_html()иesc_attr()— это защита от XSS.
Эти правила — часть общей гигиены сайта. Полный чек-лист с кодом — в статье про безопасность WordPress для новичков.
«Показать ещё»: подгружаем записи ленты через WP_Query
Вторая по популярности AJAX-задача — кнопка «Показать ещё» в блоге или каталоге. PHP-обработчик принимает номер страницы, делает обычный запрос WP_Query с параметром paged, генерирует HTML карточек в буфер вывода и отдаёт готовую строку в JSON:
add_action( 'wp_ajax_dscw_load_more', 'dscw_load_more_handler' );
add_action( 'wp_ajax_nopriv_dscw_load_more', 'dscw_load_more_handler' );
function dscw_load_more_handler() {
check_ajax_referer( 'dscw_likes', 'nonce' );
$page = isset( $_POST['page'] ) ? max( 1, absint( $_POST['page'] ) ) : 1;
$query = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 6,
'paged' => $page,
) );
if ( ! $query->have_posts() ) {
wp_send_json_error( 'Записей больше нет' );
}
ob_start();
while ( $query->have_posts() ) {
$query->the_post();
echo '<article class="post-card"><h3>' . esc_html( get_the_title() ) . '</h3></article>';
}
wp_reset_postdata();
wp_send_json_success( array( 'html' => ob_get_clean() ) );
} На клиенте остаётся вставить полученный HTML в контейнер ленты:
$.post(dscwAjax.url, {
action: 'dscw_load_more',
nonce: dscwAjax.nonce,
page: nextPage
}).done(function (response) {
if (response.success) {
$('.post-list').append(response.data.html);
nextPage++;
} else {
$('.dscw-load-more-btn').text('Это все записи').prop('disabled', true);
}
}); По этому же принципу работают бесконечные ленты и живые фильтры каталогов: меняются только аргументы WP_Query — категория, таксономии, мета-поля. Синтаксис запросов освежите в статье про полезные сниппеты для functions.php — там похожие приёмы собраны в готовые кусочки кода.
AJAX без jQuery: тот же запрос на fetch
jQuery не обязательна — современный JavaScript умеет отправлять запросы встроенной функцией fetch:
document.querySelectorAll('.dscw-like-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var data = new FormData();
data.append('action', 'dscw_like');
data.append('nonce', dscwAjax.nonce);
data.append('post_id', btn.dataset.postId);
fetch(dscwAjax.url, {
method: 'POST',
credentials: 'same-origin',
body: data
})
.then(function (response) { return response.json(); })
.then(function (result) {
if (result.success) {
btn.querySelector('.dscw-likes-count').textContent = result.data.likes;
}
})
.catch(function () {
console.log('Ошибка сети');
});
});
}); Три отличия от jQuery-версии: данные собираем в FormData (те же action, nonce и post_id), credentials: 'same-origin' передаёт куки — без них WordPress посчитает запрос гостевым даже для авторизованного пользователя, а response.json() разбирает ответ. fetch работает во всех современных браузерах, так что для новых проектов можно смело обходиться без jQuery — обработчик на сервере при этом не меняется вообще.
admin-ajax или REST API: сравнительная таблица
Оба механизма решают задачу «получить данные с сервера без перезагрузки», но подходят для разных случаев. Короткое сравнение для тех, кто выбирает подход под свою задачу:
| Критерий | admin-ajax.php | REST API |
|---|---|---|
| Порог входа для новичка | Ниже: один хук wp_ajax_{action} и одна PHP-функция | Выше: нужно понять маршруты, register_rest_route, аргументы и классы ответов |
| Нагрузка на сервер | Каждый запрос загружает ядро и админ-часть WordPress целиком | Облегчённая загрузка ядра — быстрее на больших объёмах |
| Формат ответа | Произвольный JSON через wp_send_json_success/error | Стандартизированный JSON: WP_REST_Response, коды HTTP-статусов |
| Авторизация | Куки из коробки + nonce | Куки и nonce, Application Passwords, OAuth для внешних приложений |
| Кэширование GET-запросов | Настраивается вручную и осторожно | Легче кэшировать и отдавать через CDN |
| Когда выбирать | Мелкие интерактивы в теме: лайки, «показать ещё», живые фильтры, форма голосования | Интеграции, мобильные приложения, связка нескольких сайтов, полноценные CRUD-операции |
Практический вывод: учитесь на admin-ajax — он проще и нагляднее, а когда упрётесь в нагрузку или понадобится внешнее API, мигрировать на REST API будет несложно, потому что логика обработчиков почти не изменится.
Частые ошибки новичков и быстрые решения
Семь ситуаций, с которыми сталкивается каждый, кто впервые подключает admin-ajax:
- В ответе «0». admin-ajax не нашёл обработчик: имя action в запросе не совпало с хуком либо вы зарегистрировали только
wp_ajax_*и запрос пришёл от гостя. Проверьте точное совпадение строк и добавьтеwp_ajax_nopriv_*; - К ответу прилипла цифра «0» (например, «{«success»:true}0»). В обработчике нет завершения: либо добавьте
die()/wp_die()после вывода, либо используйтеwp_send_json_*, который завершает скрипт сам; - Ответ «-1».
check_ajax_referer()отклонил nonce: ключ не передан из JS, отличается имя действия или nonce устарел (обновите страницу — ключ живёт 12–24 часа); - Ошибка 400 Bad Request. Запрос ушёл на admin-ajax.php без параметра action — проверьте, что он есть в данных и что адрес запроса именно
admin_url('admin-ajax.php'); - Пустой ответ или 500. Ошибка PHP в обработчике. Включите
WP_DEBUG_LOGи смотрите файл debug.log — точная строка с ошибкой появится там; - Лайки «не считаются» на сайте с кэшем. Агрессивный кэш или CDN отдаёт закэшированные ответы. POST-запросы к admin-ajax.php нужно исключать из кэширования в настройках плагина кэша;
- Данные из
$_POSTиспользуются без проверки. Классическая дыра: перед сохранением в базу всё прогоняйте черезabsint(),sanitize_text_field()и проверку прав, иначе первый встречный сможет записать в базу что угодно.
Если не хочется писать код: плагины с готовым AJAX
Хорошая новость: значительная часть AJAX-функциональности уже упакована в плагины, и разбираться в коде не обязательно. Что стоит знать новичку:
- Code Snippets или WPCode — плагины для безопасной вставки PHP-обработчиков из этой статьи: код хранится в базе, не зависит от темы и не сломает сайт при синтаксической ошибке (плагин предупредит перед активацией);
- Contact Form 7 и большинство форм — отправка письма происходит AJAX-ом из коробки: страница не перезагружается, а сообщение появляется под формой. Мы разбирали настройку в статье про контактную форму WordPress;
- WooCommerce — обновление корзины и пересчёт суммы работают через встроенный WC-AJAX: это тот же admin-ajax.php, просто с другим префиксом экшенов;
- WP ULike и подобные — готовые кнопки лайков и рейтингов без единой строки кода.
Полезный приём: откройте любой сайт на WordPress, нажмите F12, вкладка Network, фильтр XHR — и покликайте элементы. Вы увидите, как готовые плагины общаются с admin-ajax.php: какие передают action и nonce, что отвечают серверы. Это самый быстрый способ понять, как «это устроено внутри», без чтения документации.
Частые вопросы об AJAX в WordPress
Чем admin-ajax отличается от REST API и что выбрать новичку?
admin-ajax проще: один хук wp_ajax_{action} и одна PHP-функция. REST API мощнее: маршруты, стандартизированные ответы и меньшая нагрузка на сервер. Для мелких интерактивов в теме — лайков, кнопки «показать ещё», живых фильтров — новичку проще admin-ajax, а для интеграций, внешних приложений и тяжёлых запросов лучше REST API.
Почему AJAX-запрос возвращает «0»?
Так admin-ajax.php отвечает, когда не нашёл обработчик: имя action в запросе не совпало с хуком (wp_ajax_{action} или wp_ajax_nopriv_{action}), либо вы зарегистрировали только wp_ajax_*, а запрос пришёл от гостя. Проверьте точное совпадение имён и регистрируйте оба хука.
Почему приходит ошибка 400 Bad Request?
Запрос ушёл на admin-ajax.php без параметра action. Проверьте, что action есть в отправляемых данных, а в JS-объекте из wp_localize_script указан правильный адрес — admin_url('admin-ajax.php').
Что такое nonce и зачем он нужен в AJAX?
Nonce — одноразовый ключ защиты от подделки запросов. Он генерируется в PHP через wp_create_nonce('имя_действия'), передаётся в скрипт через wp_localize_script и проверяется в обработчике через check_ajax_referer(). Без nonce любой сторонний сайт сможет отправлять запросы от имени ваших посетителей — например, накручивать счётчики.
Как посмотреть, какие AJAX-запросы идут на сайте?
Откройте DevTools браузера (F12), вкладку Network, фильтр XHR/Fetch — и повторите действие на странице. Вы увидите запросы к admin-ajax.php, их параметры (включая action и nonce) и ответы сервера. Это главный инструмент отладки AJAX.
Влияет ли кэширование на работу AJAX?
Да. POST-запросы обычно не кэшируются, но агрессивные плагины кэша и CDN иногда кэшируют и их — тогда приходят устаревшие ответы. admin-ajax.php и запросы к нему нужно исключать из кэширования в настройках плагина кэша или на сервере.

Вывод: с чего начать работу с AJAX прямо сегодня
AJAX в WordPress — не магия, а понятный конвейер: скрипт отправляет action на admin-ajax.php, WordPress запускает функцию, повешенную на wp_ajax_{action}, функция возвращает JSON. Минимальный план для новичка:
- Создайте дочернюю тему и папку js внутри неё — туда лягут ваши скрипты;
- Скопируйте код кнопки «Полезно?» из раздела 3, замените префикс
dscw_на свой и повесьте кнопку в шаблон записи; - Проверьте работу через DevTools → Network → XHR: увидите запрос, nonce и JSON-ответ;
- Когда понадобится лента «Показать ещё» — возьмите WP_Query-обработчик из раздела 5;
- Перед запуском на живом сайте прогоните обработчик по чек-листу безопасности из раздела 4.
Больше практических материалов о разработке — в блоге delai-sait.ru и разделе «Разработка WordPress». Если нужен сайт под ключ с продуманной архитектурой и быстрым интерфейсом — посмотрите услуги разработки, примеры работ — в портфолио, а форма контактов — самый быстрый способ обсудить задачу.



