Настройки модуля в Битрикс живут в двух разных мирах: Bitrix\Main\Config\Option пишет в таблицу b_option (то, что меняет админ в UI), а Bitrix\Main\Config\Configuration читает .settings.php / .settings_extra.php (то, что деплоите вместе с кодом). Путаница между ними даёт классику: «на проде другое значение», «после очистки кеша пропало», «на сайте s2 опция не подхватилась».
Ниже — рабочий контракт Option и Configuration для модулей: get/set с siteId, дефолты, кеш опций, страницы настроек и типичные ошибки. Связка с остальным D7: сервис из ServiceLocator читает Option при старте, тяжёлые вычисления кладёте в Cache, результат операций отдаёте через Result. Уровень и путь бизнес-логов модуля удобно держать рядом с Option и писать через FileLogger.
Option vs Configuration: что куда
Option— runtime-настройки модуля/сайта в БД. Меняются из админки, CLI, миграций. Подходят для API-ключей стенда, лимитов, feature-флагов, ID инфоблоков.Configuration— статичный конфиг окружения из PHP-файлов. Подходит для DSN, Redis, почты, секретов деплоя. Не для того, что админ крутит каждый день.- Старый
COption— обёртка над тем же хранилищем. В новом коде берите D7Option, чтобы не плодить два стиля в одном модуле.
Правило на старте модуля: если значение должно отличаться на тестовом и боевом без правки кода — это Option (или секрет в Configuration через env). Если значение — часть инфраструктуры и меняется только при деплое — Configuration.
Минимум Option: get, set, delete
<?php
use Bitrix\Main\Config\Option;
final class ModuleSettings
{
private const MODULE_ID = 'vendor.demo';
public function getApiToken(string $siteId = ''): string
{
// третий аргумент — дефолт, если строки в b_option нет
return (string)Option::get(self::MODULE_ID, 'api_token', '', $siteId);
}
public function setApiToken(string $token, string $siteId = ''): void
{
Option::set(self::MODULE_ID, 'api_token', $token, $siteId);
}
public function resetApiToken(string $siteId = ''): void
{
Option::delete(self::MODULE_ID, [
'name' => 'api_token',
'site_id' => $siteId,
]);
}
}
Ключевые детали API:
moduleId— строковый ID модуля (vendor.demo), не имя класса и не путь.name— ключ опции. Держите плоский список или явный префикс (export_chunk_size), без JSON «на всё» без нужды.siteId— пустая строка значит «для всех сайтов» (общая опция). Непустой LID (s1,s2) — значение только для этого сайта.- Дефолт в
get()не пишется в БД. Он только возвращается, пока никто не сделалset().
Сайты: пустой siteId и «почему на s2 другое»
Мультисайт ломает половину интеграций именно здесь. Админ сохранил ключ на s1, а агент/крона читает с пустым siteId — получает дефолт или чужое значение.
<?php
use Bitrix\Main\Config\Option;
use Bitrix\Main\Context;
$siteId = Context::getCurrent()->getSite() ?: 's1';
// UI и публичка: читаем в контексте текущего сайта
$limit = (int)Option::get('vendor.demo', 'export_limit', '100', $siteId);
// Агент / CLI без сайта: либо явный LID, либо общая опция (siteId = '')
$limitCli = (int)Option::get('vendor.demo', 'export_limit', '100', '');
Практичная схема для модулей:
- Параметры, общие для всей установки (лицензия модуля, глобальный feature-флаг) —
siteId = ''. - Параметры витрины (инфоблок каталога, склад, webhook URL магазина) — с конкретным LID.
- В формах настроек всегда сохраняйте тот же siteId, с которым потом читаете в рантайме.
Кеш опций и инвалидация
Option::get не ходит в БД на каждый вызов вслепую: значения кешируются. После Option::set / delete кеш модуля сбрасывается штатно. Проблемы начинаются, когда вы правите b_option руками SQL или копируете дамп между стендами и ждёте мгновенного эффекта на уже поднятом PHP-FPM.
<?php
use Bitrix\Main\Config\Option;
// после массовой правки опций из миграции/CLI
Option::set('vendor.demo', 'schema_version', '3');
// если правили БД в обход API — сбросьте кеш опций явно
Option::clearCache('vendor.demo');
Не кладите в Option огромные JSON (десятки килобайт на каждый hit). Для тяжёлых структур — файл в /upload/, Highload/ORM или отдельный кеш с тегами. Option — для коротких скаляров и маленьких строк.
Страница настроек модуля
Минимальный админ-файл /bitrix/modules/vendor.demo/options.php (или путь по стандарту модуля) обычно делает три вещи: проверить права, сохранить POST в Option, отрисовать форму. Упрощённый каркас:
<?php
use Bitrix\Main\Config\Option;
use Bitrix\Main\HttpApplication;
use Bitrix\Main\Loader;
use Bitrix\Main\Localization\Loc;
Loc::loadMessages(__FILE__);
$request = HttpApplication::getInstance()->getContext()->getRequest();
$moduleId = 'vendor.demo';
if ($request->isPost() && check_bitrix_sessid()) {
Option::set($moduleId, 'api_token', trim((string)$request->getPost('api_token')));
Option::set($moduleId, 'export_limit', (string)(int)$request->getPost('export_limit'));
CAdminMessage::ShowNote(Loc::getMessage('VENDOR_DEMO_OPTIONS_SAVED'));
}
$apiToken = Option::get($moduleId, 'api_token', '');
$exportLimit = Option::get($moduleId, 'export_limit', '100');
?>
<form method="post" action="">
<?= bitrix_sessid_post() ?>
<label>API token</label>
<input type="text" name="api_token" value="<?= htmlspecialcharsbx($apiToken) ?>">
<label>Export limit</label>
<input type="number" name="export_limit" value="<?= htmlspecialcharsbx($exportLimit) ?>">
<input type="submit" class="adm-btn-save" value="Save">
</form>
На проде не забывайте: права модуля ($APPLICATION->GetGroupRight), check_bitrix_sessid(), экранирование при выводе. Секреты лучше не светить целиком в value — маска + «оставить прежнее, если поле пустое».
Configuration: .settings.php без магии
Configuration::getValue('key') читает массив из /bitrix/.settings.php и дополнений. Это не Option: админка сюда не пишет. Типичные секции — connections, cache, exception_handling, кастомные ключи вашего проекта.
<?php
use Bitrix\Main\Config\Configuration;
$cfg = Configuration::getValue('vendor.demo') ?? [];
$redisHost = (string)($cfg['redis_host'] ?? '127.0.0.1');
$redisPort = (int)($cfg['redis_port'] ?? 6379);
// точечно
$cache = Configuration::getValue('cache');
$value = Configuration::getValue('vendor.demo');
Фрагмент кастомного блока в .settings_extra.php (удобно не трогать основной файл ядра при деплое):
<?php
return [
'vendor.demo' => [
'value' => [
'redis_host' => getenv('DEMO_REDIS_HOST') ?: '127.0.0.1',
'redis_port' => (int)(getenv('DEMO_REDIS_PORT') ?: 6379),
'webhook_secret' => getenv('DEMO_WEBHOOK_SECRET') ?: '',
],
'readonly' => true,
],
];
readonly => true защищает секцию от записи через API конфигурации. Секреты — из env/CI, не из git с прод-значениями.
Типичные ошибки
- Читают с одним siteId, пишут с другим. Симптом: в форме значение есть, в агенте — пусто. Фикс: один хелпер
settingsSiteId()на весь модуль. - Кладут секреты только в Option и коммитят дамп БД. Тестовые ключи уезжают на прод. Секреты окружения — в Configuration/env, стендовые токены — в Option без коммита дампа.
- Ожидают, что
get(..., $default)сохранится сам. Дефолт виртуальный. Нужен явныйsetпри установке модуля (InstallDB/ миграция). - Правят
b_optionSQL и удивляются старому значению. ВызовитеOption::clearCache($moduleId)или перезапустите php-fpm после импорта дампа. - Пихают в Option огромный JSON. Раздуваете таблицу и кеш опций. Вынесите payload в файл/ORM, в Option оставьте ссылку или версию схемы.
- Путают Option и
$_SESSION. Option — долгоживущие настройки; сессия — состояние пользователя. Feature-флаг «включено для всех» — Option; «мастер на шаге 2» — сессия/БД пользователя.
Чек-лист в Install/Update модуля
<?php
use Bitrix\Main\Config\Option;
final class InstallOptions
{
private const MODULE_ID = 'vendor.demo';
public static function seed(): void
{
if (Option::get(self::MODULE_ID, 'schema_version', '') === '') {
Option::set(self::MODULE_ID, 'schema_version', '1');
Option::set(self::MODULE_ID, 'export_limit', '100');
}
}
public static function upgradeTo2(): void
{
if ((int)Option::get(self::MODULE_ID, 'schema_version', '1') >= 2) {
return;
}
Option::set(self::MODULE_ID, 'export_limit', '200');
Option::set(self::MODULE_ID, 'schema_version', '2');
}
}
Так вы не зависите от «админ зашёл и сохранил форму», а агенты и CLI сразу видят осмысленные значения.
Вывод
Option — контракт runtime-настроек в БД с учётом сайта и кеша. Configuration — контракт окружения из файлов деплоя. Разведите их по ролям, зафиксируйте siteId, сидьте дефолты при установке модуля — и пропадут полдня отладки «почему на s2 пусто». Дальше по D7-серии: EventManager, HttpClient, DateTime.

