В 2010 году на этом блоге был пост про AJAX в Битрикс с CPHttpRequest и jsAjaxUtil. С тех пор в платформе появился D7, а сам старый API остался — но для новых задач его выбирать не стоит. На стороне сервера актуальны три способа: через контроллер компонента, через контроллер модуля или через свой обработчик. На клиенте ко всем можно обращаться чем угодно — обычным fetch, jQuery или из Vue-компонента, бэкенд от этого не меняется.
Между CPHttpRequest и fetch в истории API был ещё промежуточный BX.ajax({ url, method, data, onsuccess, onfailure }) — низкоуровневая обёртка над XMLHttpRequest, без промисов и без автоматического CSRF. Она рабочая и встречается в старом коде, но в новых проектах проще сразу fetch или BX.ajax.runComponentAction — добавлять BX.ajax в новый код незачем.
Способ 1: BX.ajax.runComponentAction — если работа идёт через компонент
Штатный способ D7. Компонент реализует интерфейс Controllerable, действия (actions) описываются методом configureActions(), а вызов идёт через BX.ajax.runComponentAction — Битрикс сам подставляет CSRF-токен, а если тот истёк — получает новый и повторяет запрос.
class.php компонента
<?php
use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\Engine\Contract\Controllerable;
class ExampleComponent extends \CBitrixComponent implements Controllerable
{
public function configureActions(): array
{
return [
'loadMore' => [
'prefilters' => [
new ActionFilter\Csrf(),
],
],
];
}
public function loadMoreAction(int $page): array
{
return [
'items' => $this->getItems($page),
];
}
public function executeComponent()
{
$this->includeComponentTemplate();
}
}JavaScript
BX.ajax.runComponentAction('vendor:example', 'loadMore', {
mode: 'class',
data: { page: 2 },
}).then(function (response) {
console.log(response.data.items);
}, function (response) {
console.error(response.errors);
});response.data приходит уже разобранным JSON, ошибки — отдельным полем errors, промис завершается успешно (resolve) только при status: 'success', иначе — reject. Для действий модуля, не привязанных к компоненту, — способ 2 ниже.
Плюс подхода — CSRF, обработка ошибок и связь action → метод контроллера уже сделаны платформой. Минус — годится только если данные и так отдаёт компонент; для произвольного обработчика вне компонентной модели это лишний код.
Способ 2: BX.ajax.runAction — контроллер модуля, если компонента нет
Компонент под обработчик поднимать не всегда уместно, но платформенные CSRF и авторизация нужны — для этого в D7 есть контроллер модуля, отдельный от компонентных. Класс лежит в lib/Controller/ и наследует \Bitrix\Main\Engine\Controller.
.settings.php модуля
<?php
return [
'controllers' => [
'value' => [
'defaultNamespace' => '\\Vendor\\Example\\Controller',
],
'readonly' => true,
],
];lib/Controller/Item.php
<?php
namespace Vendor\Example\Controller;
use Bitrix\Main\Engine\Controller;
use Bitrix\Main\Engine\ActionFilter;
use Bitrix\Main\Error;
class Item extends Controller
{
protected function getDefaultPreFilters(): array
{
return [
new ActionFilter\HttpMethod([ActionFilter\HttpMethod::METHOD_POST]),
new ActionFilter\Csrf(),
];
}
public function addAction(array $fields): ?array
{
$id = $this->saveItem($fields);
if (!$id) {
$this->addError(new Error('Не удалось сохранить запись', 'ITEM_SAVE_ERROR'));
return null;
}
return ['id' => $id];
}
}Метод должен быть публичным и оканчиваться на Action — остальное имя становится именем action. Параметры (array $fields выше) платформа сама достаёт из запроса и приводит к нужному типу — так же, как в configureActions() компонента. addError() + return null дают тот же формат ошибки, что и у компонента: {"status": "error", "errors": [...]}; обычный return массива или объекта, реализующего Arrayable/JsonSerializable, — {"status": "success", "data": ...}.
По умолчанию getDefaultPreFilters() (без переопределения) уже включает Authentication, HttpMethod(GET, POST) и Csrf — то есть неавторизованный пользователь получит 401 без единой строчки кода. Для публичного действия (форма для гостя, счётчик) Authentication из фильтров нужно убрать явно, как в примере выше — иначе анонимный запрос отвалится ещё до вызова метода.
JavaScript
BX.ajax.runAction('vendor:example.Item.add', {
data: { fields: { NAME: 'test' } },
}).then(function (response) {
console.log(response.data.id);
}, function (response) {
console.error(response.errors);
});Формат action — вендор:модуль.ИмяКлассаКонтроллера.имяМетодаБезAction. CSRF-токен BX.ajax.runAction подставляет и обновляет сам — так же, как runComponentAction в способе 1.
Способ 3: свой обработчик на fetch — если и контроллер не нужен
Контроллер модуля предполагает саму структуру модуля (lib/, .settings.php) — для одноразового служебного скрипта вне модуля это лишнее. Тогда актуальная версия того, что раньше делал CPHttpRequest, — обычный fetch к своему PHP-файлу с CSRF и разбором ответа руками.
Серверный обработчик
<?php
define('STOP_STATISTICS', true);
require($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/prolog_before.php');
if (!check_bitrix_sessid()) {
(new \Bitrix\Main\Engine\Response\Json(['error' => 'bad sessid'], JSON_UNESCAPED_UNICODE))->send();
require($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/epilog_after.php');
exit;
}
$param = htmlspecialchars($_POST['param'] ?? '', ENT_QUOTES, 'UTF-8');
(new \Bitrix\Main\Engine\Response\Json(['param' => $param], JSON_UNESCAPED_UNICODE))->send();
require($_SERVER['DOCUMENT_ROOT'].'/bitrix/modules/main/include/epilog_after.php');STOP_STATISTICS по-прежнему исключает запрос из счётчика статистики Битрикс — из констант 2010 года она не единственная, что дожила до наших дней, но самая частая в AJAX-обработчиках. \Bitrix\Main\Engine\Response\Json сам ставит заголовок Content-Type: application/json; charset=UTF-8 — кодировка windows-1251 из старого поста для новых проектов не нужна, D7 работает в UTF-8. После send() важно ничего больше не выводить — epilog_after.php в конце обязателен (закрывает сессию, финализирует буфер), а не echo/var_dump для отладки.
JavaScript
fetch('/ajax/example.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
param: 'test_param',
sessid: BX.bitrix_sessid(),
}),
})
.then((r) => {
if (!r.ok) {
throw new Error(`HTTP ${r.status}`);
}
return r.json();
})
.then((data) => {
document.getElementById('container').innerHTML = data.param;
});fetch не отклоняет промис на HTTP-ошибках (404, 500) — только на сетевой сбой, поэтому r.ok нужно проверять руками, иначе .json() на пустом теле ответа падает с непонятной ошибкой. BX.bitrix_sessid() отдаёт текущий CSRF-токен на клиенте — без него check_bitrix_sessid() на сервере вернёт false. Индикатор загрузки делаем своей вёрсткой — jsAjaxUtil.ShowLocalWaitWindow() из старого поста тоже устарел вместе с остальным API.
Для загрузки файла тем же способом вместо URLSearchParams — FormData, Content-Type при этом не указываем, браузер сам подставит правильный boundary:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
formData.append('sessid', BX.bitrix_sessid());
fetch('/ajax/example.php', {
method: 'POST',
body: formData,
}).then((r) => r.json());Тот же $_POST['sessid'] и $_FILES['file'] на сервере — check_bitrix_sessid() из примера выше работает без изменений, файл читается штатным $_FILES. BX.ajax.runComponentAction тоже принимает FormData в data — для action-метода компонента ничего дополнительно готовить не нужно.
jQuery — если библиотека уже в проекте
Для старых проектов, где jQuery уже подключён, $.ajax — прямая замена CPHttpRequest/fetch из способа 3. Обращается к тому же обработчику, серверная часть не меняется:
$.ajax({
url: '/ajax/example.php',
type: 'POST',
data: {
param: 'test_param',
sessid: BX.bitrix_sessid(),
},
success: function (data) {
$('#container').html(data.param);
},
error: function (xhr) {
console.error(xhr.responseText);
},
});Разница с fetch — только синтаксис. Для BX.ajax.runComponentAction (способ 1) jQuery не нужен вообще — это уже готовый промис, оборачивать его в $.ajax незачем.
Vue-компонент — та же логика, реактивный клиент
Если фронтенд собирается на Vue (виджет, форма, личный кабинет) внутри шаблона компонента Bitrix, AJAX-логика та же самая — просто вызывается из метода компонента, а не напрямую из обработчика клика.
Composition API (актуальный вариант, Vue 3)
<script setup>
import { ref, onMounted } from 'vue';
const items = ref([]);
const loading = ref(false);
async function loadMore(page) {
loading.value = true;
const response = await BX.ajax.runComponentAction('vendor:example', 'loadMore', {
mode: 'class',
data: { page },
});
items.value.push(...response.data.items);
loading.value = false;
}
onMounted(() => loadMore(1));
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id">{{ item.title }}</li>
</ul>
<button :disabled="loading" @click="loadMore(2)">Ещё</button>
</template>BX.ajax.runComponentAction возвращает промис — в async-методе Vue его достаточно await-нуть. Для этих двух примеров axios не требуется — в большом SPA со своим API-слоем он всё ещё может быть оправдан, но конкретно для контроллера компонента или обработчика на fetch это лишняя зависимость.
Options API (старый код на Vue 2 или ранний Vue 3)
Та же логика без <script setup>:
export default {
data() {
return { items: [], loading: false };
},
methods: {
async loadMore(page) {
this.loading = true;
const response = await BX.ajax.runComponentAction('vendor:example', 'loadMore', {
mode: 'class',
data: { page },
});
this.items.push(...response.data.items);
this.loading = false;
},
},
mounted() {
this.loadMore(1);
},
};Что выбрать
- Данные и так приходят из компонента (каталог, список, фильтр) —
BX.ajax.runComponentAction, CSRF и разбор ответа не пишутся руками. - Нужен отдельный обработчик без привязки к компоненту, но с платформенными CSRF/авторизацией — контроллер модуля (
BX.ajax.runAction),getDefaultPreFilters()под задачу. - Разовый служебный скрипт вне модульной структуры — свой на
fetch, CSRF черезcheck_bitrix_sessid()/BX.bitrix_sessid()проверяется вручную. - jQuery уже в проекте —
$.ajaxвместоfetchдля своего обработчика; дляrunComponentAction/runActionбез разницы, оба и так возвращают промис. - Фронтенд на Vue — тот же
BX.ajax.runComponentAction/fetch, просто вызванный из метода компонента (Composition API — актуальный вариант, Options API — для старого кода). CPHttpRequestиjsAjaxUtil— только если поддерживается старый проект, где они уже есть; в новый код их добавлять незачем.
Независимо от способа: если запрос меняет данные — метод POST, GET оставляем только для чтения.
Если AJAX-запрос не срабатывает и непонятно почему — отдельный старый пост про отладку AJAX в админке Битрикс.
