Development of custom AJAX handlers for 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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.