Developing Custom AJAX Handlers for 1C-Bitrix
There are two scenarios where custom AJAX handlers are specifically needed rather than standard D7 controllers. The first — integration with legacy code that has not yet been migrated to D7, and an AJAX interface needs to be provided without refactoring. The second — high-load endpoints where complete control over every millisecond matters: minimal core initialization, direct database queries, maximum response speed.
Selective Bitrix Core Initialization
The standard header.php initializes the entire site — modules, session, permissions, templates. For an AJAX endpoint, all of this is unnecessary overhead. A custom handler initializes only what is needed:
<?php
// /local/ajax/catalog-prices.php
define('NO_KEEP_STATISTIC', true); // do not write statistics
define('NO_AGENT_STATISTIC', true); // do not run agents
define('DisableEventsCheck', true); // skip part of initialization
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
// Only necessary modules
\Bitrix\Main\Loader::includeModule('catalog');
\Bitrix\Main\Loader::includeModule('sale');
This approach reduces initialization time from 50–80 ms to 10–15 ms on a cold start.
Deep Dive: Structure of a Custom Handler
A custom handler is a regular PHP file with an explicit structure: method check, CSRF, authorization, action routing, response building:
// Method check
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
die(json_encode(['error' => 'Method Not Allowed']));
}
// CSRF check
if (!check_bitrix_sessid()) {
http_response_code(403);
die(json_encode(['error' => 'CSRF check failed']));
}
// Authorization
global $USER;
if (!$USER->IsAuthorized()) {
http_response_code(401);
die(json_encode(['error' => 'Unauthorized']));
}
// Routing
$action = $_POST['action'] ?? '';
$result = match($action) {
'get_price' => getPriceAction((int)($_POST['product_id'] ?? 0)),
'check_stock' => checkStockAction((int)($_POST['product_id'] ?? 0)),
default => ['error' => 'Unknown action'],
};
header('Content-Type: application/json; charset=utf-8');
echo json_encode($result, JSON_UNESCAPED_UNICODE);
This pattern — with an explicit match on action — is preferable to eval or call_user_func on user input.
Handler Security
Input validation. Each parameter undergoes explicit typing and range checking:
function getPriceAction(int $productId): array {
if ($productId <= 0) {
return ['error' => 'Invalid product ID'];
}
// Check access to the product for the current user
if (!\CIBlockElement::GetPermission($productId) >= 'R') {
return ['error' => 'Access denied'];
}
// ...
}
Rate limiting. For public endpoints (without authorization), protection against abuse is mandatory: no more than N requests from a single IP per M seconds. Implemented via APC / APCu or Redis as a fast counter storage.
Error logging. PHP exceptions inside the handler must not appear in the HTTP response — only in the log. Wrap the logic in a try/catch with writes to \Bitrix\Main\Diag\Debug::writeToFile.
Response Caching
For data that changes infrequently (e.g., product attributes), the response is cached:
$cacheManager = \Bitrix\Main\Application::getInstance()->getManagedCache();
$cacheKey = "product_attrs_{$productId}";
if (!$cacheManager->read(3600, $cacheKey)) {
$data = loadProductAttributes($productId);
$cacheManager->set($cacheKey, $data);
} else {
$data = $cacheManager->get($cacheKey);
}
Bitrix's managed cache (getManagedCache) supports tagged invalidation — when a product changes, the tag is cleared automatically.
When to Choose a Custom Handler vs D7 Controller
| Criterion | Custom Handler | D7 Controller |
|---|---|---|
| Maximum speed | Better | Worse (more initialization) |
| Code structure | Arbitrary | Standardized |
| Testability | Harder | Better |
| Maintainability for a new developer | Harder | Easier |
| Compatibility with legacy code | Better | Requires refactoring |
Development Timeline
| Scale | Scope | Timeline |
|---|---|---|
| Basic | 5–10 endpoints + CSRF + basic caching | 1–2 weeks |
| Medium | + rate limiting + detailed logging + tests | 2–4 weeks |
| Extended | + hybrid approach (some on D7, some custom) + monitoring | 4–6 weeks |
For new projects, we recommend starting with D7 controllers and moving to custom handlers only for specific endpoints where performance is critical and confirmed by a profiler.







