Свой модуль в Битрикс: типичные ошибки и как их избежать
Модуль в /local/modules не появляется в списке, падает при установке или стирает таблицы при удалении. Разбираем шесть ошибок, на которых спотыкаются чаще всего, и показываем, как их закрыть.

Вы положили модуль в /local/modules, а в списке его нет. Или он встал, но при удалении унёс таблицу с данными. Или вы добавили поле в схему, выкатили на прод, и база не изменилась. Причина одна: модуль в Битрикс не папка с кодом, а запись в базе плюс жизненный цикл из установки, удаления и повторной установки. Ошибки живут именно в этом цикле.
Модуль, init.php или компонент
Ориентир: «1С-Битрикс: Управление сайтом», ядро D7*, PHP 8.x. С какой сборки главного модуля доступен каждый класс, сверяйте в документации D7.
| Подход | Установка и удаление | Перенос на другой сайт | Цена |
|---|---|---|---|
| init.php | Нет, код работает всегда | Копировать файл и править вручную | Код разрастается, выключить нельзя |
| Компонент в /local/components | Копирование папки | Копирование папки | Не подходит для таблиц и событий |
| Модуль в /local/modules | Есть, с правами и настройками | Копирование и установка в админке | Установщик надо писать и тестировать |
Нужны своя таблица, обработчики событий или настройки, которые можно выключить, делайте модуль. Нужно вывести блок на странице, хватит компонента. Одна правка в одном событии лежит в init.php.
Установщик, на котором ломается больше всего
Ниже каркас install/index.php. В комментариях отмечено, где сидят ошибки 1 – 3.
<?php
// /local/modules/vendor.module/install/index.php
use Bitrix\Main\Application;
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\ModuleManager;
use Bitrix\Main\ORM\Entity;
use Vendor\Module\Handlers;
use Vendor\Module\ItemTable;
Loc::loadMessages(__FILE__);
// Ошибка 1: имя класса = ID модуля, точка заменена на «_»
class vendor_module extends CModule
{
public $MODULE_ID = 'vendor.module';
public function __construct()
{
include __DIR__ . '/version.php';
$this->MODULE_VERSION = $arModuleVersion['VERSION'];
$this->MODULE_VERSION_DATE = $arModuleVersion['VERSION_DATE'];
$this->MODULE_NAME = Loc::getMessage('VENDOR_MODULE_NAME');
$this->MODULE_DESCRIPTION = Loc::getMessage('VENDOR_MODULE_DESCRIPTION');
}
public function DoInstall()
{
// Ошибка 2: сначала регистрация, потом обращение к ORM-классу
ModuleManager::registerModule($this->MODULE_ID);
Loader::includeModule($this->MODULE_ID);
if (!Application::getConnection()->isTableExists(ItemTable::getTableName())) {
Entity::getInstance(ItemTable::class)->createDbTable();
}
EventManager::getInstance()->registerEventHandler(...$this->handlerArgs());
}
public function DoUninstall()
{
global $APPLICATION;
$request = Application::getInstance()->getContext()->getRequest();
if ((int)$request->get('step') < 2) {
// Шаг 1: форма с чекбоксом savedata
$APPLICATION->IncludeAdminFile(
Loc::getMessage('VENDOR_MODULE_UNINSTALL_TITLE'),
__DIR__ . '/unstep1.php'
);
return;
}
EventManager::getInstance()->unRegisterEventHandler(...$this->handlerArgs());
Loader::includeModule($this->MODULE_ID);
if ($request->get('savedata') !== 'Y') {
Application::getConnection()->dropTable(ItemTable::getTableName());
}
ModuleManager::unRegisterModule($this->MODULE_ID);
}
// Ошибка 3: один источник аргументов для регистрации и снятия
private function handlerArgs(): array
{
return [
'iblock',
'OnAfterIBlockElementUpdate',
$this->MODULE_ID,
Handlers::class,
'onElementUpdate',
];
}
}
Ошибка 1. Имя класса не совпадает с ID модуля
Битрикс ищет установщик по классу из install/index.php. Назовёте его VendorModule вместо vendor_module, и модуль не попадёт в список. Проверьте три вещи:
- класс установщика называется vendor_module для ID vendor.module;
- рядом лежит install/version.php с массивом $arModuleVersion и ключами VERSION и VERSION_DATE;
- ID записан строчными буквами в формате «партнёр.модуль» и одинаков в папке и в $MODULE_ID.
Ошибка 2. Таблица создаётся не в том порядке и удаляется без вопросов
Сначала registerModule, затем includeModule, и только потом обращение к ORM-классу из lib/. Иначе includeModule вернёт false, а вызов ItemTable, скорее всего, закончится фатальной ошибкой. Вторая половина ошибки в удалении. Если форма «сохранить данные» не показана, условие savedata никогда не выполнится, и таблица исчезнет молча. Страница unstep1.php должна отправлять step=2, ID модуля, uninstall=Y, sessid и чекбокс savedata.
Деинсталлятор без вопроса о данных решает за владельца сайта, и решает обычно неверно.
Ошибка 3. Обработчики событий остаются в базе
registerEventHandler пишет обработчик в базу (таблица b_module_to_module), а не в память. Модуль удалён, запись осталась. Если при снятии передать другие аргументы, запись не найдётся, поэтому в каркасе они лежат в одном методе. Вторая ловушка: вы обновляете элемент внутри OnAfterIBlockElementUpdate, и событие срабатывает снова. Ставьте статический флаг в классе обработчика. И держите обработчик лёгким: он выполняется при каждом обновлении элемента.
Ошибка 4. Кодировка и строки интерфейса
Сайт в windows-1251, а файлы модуля в UTF-8 (или наоборот): в админке вместо названия набор символов. Кодировка файлов должна совпадать с кодировкой сайта, проверьте её до первой строки кода. Строки интерфейса вынесите в lang/ru/, структура зеркалит модуль: для install/index.php это lang/ru/install/index.php. Ключи начинайте с префикса модуля, общие вроде TITLE могут затирать друг друга.
Ошибка 5. Настройки без проверки прав и сессии
Страница options.php открывается не только вам. Проверьте право на модуль и сессию до сохранения:
<?php
// /local/modules/vendor.module/options.php
use Bitrix\Main\Application;
use Bitrix\Main\Config\Option;
global $APPLICATION;
$moduleId = 'vendor.module';
if ($APPLICATION->GetGroupRight($moduleId) < 'W') {
return;
}
$request = Application::getInstance()->getContext()->getRequest();
// В форме должен быть bitrix_sessid_post()
if ($request->isPost() && check_bitrix_sessid()) {
Option::set($moduleId, 'api_url', (string)$request->getPost('api_url'));
}
Запросы собирайте через ORM: getList с фильтром ['=CODE' => $code] подставляет значение безопасно. Если нужен сырой SQL, пропускайте значения через Application::getConnection()->getSqlHelper()->forSql(), а не клейте строкой.
Ошибка 6. Правки в ядре и схема без миграций
Файлы в /bitrix/modules/main и других штатных модулях перезапишет первое же обновление. Подменяйте поведение событиями. Со схемой другая беда: DoInstall на установленном модуле повторно не вызывается, поэтому поле, добавленное в getMap(), в базе не появится. Храните версию схемы в опции модуля и применяйте недостающие миграции по порядку из отдельного скрипта. Формат обновлений для Маркетплейса описан в документации.
Проверка перед выкладкой
- Установите модуль на копии сайта: он в списке, ошибок нет.
- Удалите с сохранением данных и поставьте снова: таблица и записи на месте.
- Удалите без сохранения: таблицы нет, строк модуля в b_module_to_module нет.
- Откройте options.php под пользователем без прав: настройки не сохраняются.
- Пройдите цикл с включённым выводом ошибок: в логе PHP чисто.
Что делать дальше
Прогоните цикл «установка, удаление, установка» на копии боевой базы. Замерьте время обновления элемента инфоблока до и после установки модуля: так видна цена обработчиков. ORM и агенты здесь не разбирались, это отдельные темы. Схему миграций лучше оформить до первого релиза, а не после первой жалобы.
* D7 — современное ядро Битрикс с пространствами имён, ORM и автозагрузкой классов. Отличается от старого API вида CIBlockElement.
Транскрибатор встреч: Запись созвона или диктофона — в текст с разделением по спикерам, протоколом и списком задач.
Спросить за 25 ₽Источники: 1С-Битрикс: документация D7 для разработчиков
Частые вопросы
Где хранить модуль: в /local/modules или в /bitrix/modules?
Модуль не появился в списке. Что проверить?
У меня другая версия Битрикс, и класс из примера не найден. Что делать?
Что будет с данными, если удалить модуль?
Как обновить схему таблицы после релиза?
Какие подводные камни ждут модуль в проде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.