Development of Event Handlers for 1C-Bitrix
The event model in Bitrix is not just "attach a hook and forget". It's an architectural layer that permeates all core operations: saving an order, updating an iblock element, user authorization, sending mail. An incorrectly written handler slows down every hit, creates circular calls, or silently breaks adjacent logic. Let's understand how the system works and how to do it right.
Anatomy of Events in Bitrix
The core generates events at key points via the \Bitrix\Main\EventManager class. Each event is tied to a module and has a name. A handler is registered with:
use Bitrix\Main\EventManager;
EventManager::getInstance()->addEventHandler(
'sale', // source module
'OnSaleOrderBeforeSaved', // event name
[\MyProject\Sale\OrderHandler::class, 'onBeforeSave'], // callable
100 // sort — priority
);
Registration happens in /local/php_interface/init.php — this file is included on every hit before page formation. For portable logic, handlers register in the module's installEvents() method and uninstall in uninstallEvents().
Before-events (OnBefore*) — allow you to modify data before save or abort the operation. The handler returns an \Bitrix\Main\EventResult object:
return new EventResult(EventResult::ERROR, 'Invalid field value', 'my_module');
After-events (OnAfter*) — for reacting to an action already performed: send notification, write log, update related entities. Return value doesn't affect the operation result.
Key Events by Module
| Module | Event | Trigger | Type |
|---|---|---|---|
iblock |
OnBeforeIBlockElementAdd |
Before adding iblock element | Before |
iblock |
OnAfterIBlockElementUpdate |
After element update | After |
iblock |
OnBeforeIBlockElementDelete |
Before element deletion | Before |
sale |
OnSaleOrderBeforeSaved |
Before order save | Before |
sale |
OnSalePayOrder |
On order payment | After |
sale |
OnSaleStatusOrder |
On order status change | After |
sale |
OnSaleBasketItemRefreshData |
On basket item recalc | Before |
catalog |
OnBeforePriceUpdate |
Before price update | Before |
catalog |
OnAfterCatalogImport1C |
After 1C exchange completes | After |
main |
OnAfterUserAuthorize |
After authorization | After |
main |
OnBeforeProlog |
Before page generation | Before |
main |
OnEpilog |
After page sent | After |
Full event catalog for each module is in /bitrix/modules/{module}/lib/events.php or /bitrix/modules/{module}/include.php.
Code Structure: How to Organize Dozens of Handlers
A real project accumulates 20–50 handlers. Without structure, init.php becomes an unmanageable dump. Working scheme:
/local/php_interface/
├── init.php → only file inclusion
├── handlers/
│ ├── SaleHandlers.php → sale module event registration
│ ├── IblockHandlers.php → iblock module event registration
│ └── MainHandlers.php → main module event registration
└── classes/
├── OrderEventHandler.php → order handler logic
├── CatalogEventHandler.php
└── UserEventHandler.php
init.php contains only require_once — no logic. Registration file (SaleHandlers.php) — only addEventHandler calls. Handler classes — static methods with single responsibility.
Critical Errors
Circular calls. Handler OnAfterIBlockElementUpdate internally updates an iblock element → event fires again → infinite recursion. Protection via static flag:
class CatalogEventHandler
{
private static bool $inProgress = false;
public static function onAfterElementUpdate(array $arFields): void
{
if (self::$inProgress) {
return;
}
self::$inProgress = true;
try {
// update logic
} finally {
self::$inProgress = false;
}
}
}
Heavy operations in Before-events. OnSaleOrderBeforeSaved fires several times during single checkout (each recalc). HTTP request to external API inside it — guaranteed slowdown. Heavy operations (external requests, mass UPDATE) go to After-events or agents.
No exception handling. Unhandled exception in handler can crash the page. Minimum — try/catch with logging via \Bitrix\Main\Diag\Debug::writeToFile().
Execution order. Multiple handlers for one event execute in ascending sort order. If your handler depends on another's result — set sort explicitly. Default — 100.
Debugging and Profiling
Use the perfmon module (performance panel in admin area) for diagnostics. Shows which handlers are registered for each event and how much time each takes.
Temporary debug:
\Bitrix\Main\Diag\Debug::writeToFile(
['event' => 'OnSaleOrderBeforeSaved', 'data' => $arFields],
'order_handler',
'/local/logs/sale.log'
);
Timeline
| Task | Duration |
|---|---|
| 1–3 simple handlers (notifications, logging, field filling) | 2–3 days |
| Complex logic (external service integration, order validation, price recalc) | 5–10 days |
| Refactoring existing handlers in init.php (audit, reorganization, conflict resolution) | 1–2 weeks |
Each handler on OnBeforeProlog executes on every site hit. Adding 50 ms to each hit means adding hours of lost user time per day. The event model is powerful, but requires discipline in architecture and constant performance monitoring.







