# Bitrix Events

> Covers Bitrix event system — new model (Bitrix\Main\Event, EventResult, EventManager::addEventHandler, make:event, make:eventhandler) and old model (OnBefore*/OnAfter* hooks in CIBlock, CUser, CSale and other classic APIs). Applied when integrating modules, entity lifecycle hooks, publishing custom events and subscribing to events of other modules. Key terms — Event, EventManager, EventResult, OnBefore, OnAfter, handler, subscriber, addEventHandler.

- Skill: `bxmaximum/bitrix-events` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bxmaximum/bitrix-events`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bxmaximum/bitrix-events/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: bxmaximum (https://skillmd.com/u/bxmaximum)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/bxmaximum/bitrix-events

---


# Bitrix Events

There are **two event models**: new (OOP, `Event` + `EventResult`) and old (string code + handler returning bool/array). For new code — use the new model. The old model is used for compatibility with the kernel (`OnBeforeUserAdd`, `OnPageStart`, ...).

## New Model — Publishing Your Event

### 1. Create Event Class

```bash
php bitrix/bitrix.php make:event PostCreated -m vendor.blog
```

**Since main 25.900** for `make:*`. On older versions, scaffold the class manually.

File: `/local/modules/vendor.blog/lib/Public/Event/Post/PostCreatedEvent.php`.

```php
<?php declare(strict_types=1);

namespace Vendor\Blog\Public\Event\Post;

use Bitrix\Main\Event;

final class PostCreatedEvent extends Event
{
    public function __construct(
        public readonly int $postId,
        public readonly int $authorId,
        public readonly string $title,
    ) {
        parent::__construct('vendor.blog', self::class, [
            'postId'   => $this->postId,
            'authorId' => $this->authorId,
            'title'    => $this->title,
        ]);
    }
}
```

### 2. Dispatch Event from Service

```php
use Vendor\Blog\Public\Event\Post\PostCreatedEvent;

$event = new PostCreatedEvent($post->getId(), $post->getAuthorId(), $post->getTitle());
$event->send();

foreach ($event->getResults() as $result)
{
    if ($result->getType() === \Bitrix\Main\EventResult::ERROR)
    {
        $this->logger->warning('Subscriber failed', ['errors' => $result->getParameters()]);
    }
}
```

### 3. Write Handler

```bash
php bitrix/bitrix.php make:eventhandler NotifyAuthor \
    --event-module=vendor.blog --handler-module=vendor.notify
```

EventManager invokes handlers via `call_user_func_array` (class + method). Handlers are **not** created by `ServiceLocator` — do not rely on constructor DI. Resolve services inside the handler method.

```php
<?php declare(strict_types=1);

namespace Vendor\Notify\Internals\Integration\Blog\EventHandler;

use Bitrix\Main\DI\ServiceLocator;
use Bitrix\Main\Event;
use Bitrix\Main\EventResult;
use Vendor\Blog\Public\Event\Post\PostCreatedEvent;
use Vendor\Notify\Application\Service\Notifier;

final class NotifyAuthorHandler
{
    public static function handle(Event $event): EventResult
    {
        if (!$event instanceof PostCreatedEvent)
        {
            return new EventResult(EventResult::UNDEFINED);
        }

        /** @var Notifier $notifier */
        $notifier = ServiceLocator::getInstance()->get(Notifier::class);
        $result = $notifier->notifyAuthor($event->authorId, $event->title);

        return new EventResult(
            $result->isSuccess() ? EventResult::SUCCESS : EventResult::ERROR,
            $result->getErrorMessages(),
        );
    }
}
```

### 4. Register Handler in `install/index.php`

```php
\Bitrix\Main\EventManager::getInstance()->registerEventHandler(
    fromModule: 'vendor.blog',
    eventType: \Vendor\Blog\Public\Event\Post\PostCreatedEvent::class,
    toModuleId: 'vendor.notify',
    toClass: \Vendor\Notify\Internals\Integration\Blog\EventHandler\NotifyAuthorHandler::class,
    toMethod: 'handle',
);
```

In `DoUninstall()` — **mandatory** `unRegisterEventHandler` with the same parameters.

## Old Model (Compatibility)

Old events have string names: `OnBeforeUserAdd`, `OnAfterUserAdd`, `OnEpilog`, `OnPageStart`. They pass an array/object of parameters and return:

- `true`/nothing — continue;
- `false` + `$APPLICATION->ThrowException(...)` — cancel action;
- array with `'FIELDS' => [...]` — modify fields (for `OnBefore*`).

Registering handlers accepting the **old** signature:

```php
EventManager::getInstance()->registerEventHandlerCompatible(
    'main',
    'OnAfterUserAdd',
    'vendor.blog',
    \Vendor\Blog\Internals\Integration\Main\EventHandler\OnAfterUserAddHandler::class,
    'handle',
);
```

The new `registerEventHandler` also works with old events but adapts them to the `Event $event` signature — parameters are retrieved via `$event->getParameter('fields')`, modification via `EventResult`.

## Order and Chain of Handlers

- Handlers run in ascending `$sort` order (default `100`).
- `addEventHandler($fromModuleId, $eventType, $callback, $includeFile = false, $sort = 100)` — `$sort` is the **5th** parameter.
- `registerEventHandler($fromModuleId, $eventType, $toModuleId, $toClass = '', $toMethod = '', $sort = 100, ...)` — `$sort` is the **6th** parameter.
- The new API (`Event::send()`) collects results from all handlers — the chain is not interrupted even if one returns `ERROR`.
- In the old API, a single `false` can interrupt the action (depends on the calling code in the kernel).

## Dynamic Subscription in One Process

For hooks that don't need to be stored in the DB (tests, one-time wrappers):

```php
EventManager::getInstance()->addEventHandler(
    'main',
    'OnAfterUserAdd',
    fn (array $fields) => /* ... */,
);
```

Such registration lives until the end of the request.

## Checklist

- [ ] Public event files — in `/lib/Public/Event/<Aggregate>/`.
- [ ] Handlers of other modules' events — in `/lib/Internals/Integration/<OtherModule>/EventHandler/`.
- [ ] Registration and unregistration of handlers as a pair in `DoInstall`/`DoUninstall`.
- [ ] Custom events use `Bitrix\Main\Event` + `EventResult` instead of returning arrays.
- [ ] Handler has no constructor DI — resolve services inside the method via `ServiceLocator::get()`.
- [ ] Handler is idempotent and does not crash — wrap everything in `try/catch` with logging.
- [ ] Heavy logic is moved to a queue (`Messenger` via `$message->send()`), handler only dispatches a task.

