Play
online · 30 мин
Туториалы 22 мин чтения 18.09.2026

Свойства инфоблока в Битрикс: типы, настройка через API и вывод в шаблоне

Разбираем, чем свойства инфоблока отличаются от полей элемента и почему на каталоге в 30 000 SKU ошибка в структуре хранения обходится дорого. Типы свойств, настройка и вывод в шаблоне — без воды и лишней теории.

Свойства инфоблока в Битрикс: что это и зачем их вообще трогать

Когда к нам приходит проект с каталогом на 30 000 SKU, первое, что мы разбираем — где проходит граница между полями элемента и свойствами инфоблока. Ошибка на этом этапе стоит дорого: её потом не исправить миграцией за один вечер, потому что от структуры хранения зависят фильтры, шаблоны, интеграция с 1С и модуль поиска. Поля элемента — это жёсткий каркас, который задан ядром: NAME, CODE, PREVIEW_TEXT, DETAIL_TEXT, ACTIVE, SORT, IBLOCK_SECTION_ID. Их нельзя расширить, не трогая ядро, и нельзя завести «поле бренд» — такого поля в Битрикс просто нет.

А вот бренд, размер, цвет, габариты, поставщик, серия, коллекция — всё это свойства. Каталог на 30k SKU без свойств не живёт: контент-менеджеру нужно фильтровать по бренду, менеджеру — выгружать в маркетплейс по поставщику, а покупателю — подбирать кроссовки по размеру. Каждое такое измерение — отдельное свойство со своим типом и настройками. Именно свойства дают то, чего не дают поля: расширяемость без правки кода и участие в умном фильтре. Ниже разберём, чем технически отличаются два способа хранить данные и как мы обычно делим их в проектах Paradigma.

Критерий Поле элемента Свойство инфоблока
Где хранится Таблица b_iblock_element Таблица b_iblock_element_property
Как фильтруется Прямо в GetList по ключу Через PROPERTY_* или отдельный запрос
Добавить без правки кода Нет — только миграция ядра Да — через админку или API
Умный фильтр Не участвует Участвует штатно
Множественность Нет Да — флаг MULTIPLE

В проектах Paradigma мы держим простое правило: то, что ищет пользователь и что приходит из 1С как атрибут товара, — свойство; то, что описывает сам элемент как запись, — поле. Название, символьный код, краткое и детальное описание, активность, привязка к разделу — это поля, их трогает только контент-менеджер. Бренд, размерная сетка, цвет, вес, страна производства, артикул поставщика — свойства, потому что по ним фильтруют и их передаёт обмен.

Граница не всегда очевидна. Цену мы не храним в свойстве — для неё есть CATALOG_PRICE и типы цен, иначе теряем скидки и пересчёт. Остатки — тоже не свойство, а CATALOG_QUANTITY. А вот «гарантия производителя» или «материал подошвы» — уже свойство: по ним может быть фильтр, и они не участвуют в торговых расчётах. Если сомневаетесь — спросите, будет ли по этому атрибуту фильтрация или выгрузка. Если да — свойство. Если это служебная метка для логики — возможно, хватит поля или отдельной таблицы, но это уже редкость.

Отдельно про множественность: у свойства можно включить MULTIPLE = Y, и тогда у товара будет несколько значений — например, несколько тегов или несколько совместимых моделей. У поля такой возможности нет. Это ещё один аргумент в пользу свойств там, где данные по природе своей множественные. Об этом ниже — в разделе про типы свойств, где разберём, какой тип под какую задачу брать.

Типы свойств инфоблока: какой выбрать под конкретную задачу

Типичный сценарий: контент-менеджер просит «сделать фильтр по цвету и материалу», вы открываете настройки свойства и видите выпадающий список из двух десятков типов. Выбираешь наугад — через месяц выясняется, что умный фильтр не подхватывает значения, а переделать на живой базе уже нельзя.

У нас в практике выбор типа — это решение на этапе проектирования инфоблока, а не «потом поправим». Смена типа не конвертирует данные автоматически: значения остаются в старой таблице, а новое свойство читается как пустое. Поэтому мы всегда сначала смотрим, какие задачи будут у свойства: только хранение и вывод, участие в умном фильтре, связь с другими сущностями или загрузка файлов.

Дальше — матрица типов и три группы задач, под которые они подбираются.

Тип свойства Под какую задачу Где хранится значение В умном фильтре Особенности
Строка Артикул, краткий текст b_iblock_element_property Да (по подстроке) Лимит длины, нет сортировки по алфавиту
Число Вес, размер, цена-дубль b_iblock_element_property Да (диапазон) Хранится как строка, но фильтруется численно
Список Цвет, материал, сезон b_iblock_element_property + b_iblock_property_enum Да (фасетный) Значения предзаданы, ID привязаны к свойству
Привязка к элементам Похожие товары, аксессуары b_iblock_element_property (ID элементов) Да Тянет поля связанного элемента, риск рекурсии
Привязка к разделам Доп. категории, теги-разделы b_iblock_element_property (ID разделов) Да Работает с любым инфоблоком, а не только своим
Файл PDF-инструкции, чертежи b_iblock_element_property (ID файла) Нет Множественное — галерея, одиночное — один файл
Привязка к HL-блоку Справочник брендов, регионов b_iblock_element_property (ID записи HL) Ограниченно Нужен отдельный HL-инфоблок и свойство-ключ
Справочник Крупные классификаторы (МКТУ, ОКЕИ) b_iblock_element_property Да Требует модуля catalog, значения из HL

Строка, число и список: базовые типы для каталога

Девяносто процентов свойств каталога закрываются тремя типами. Строка — для всего, что не участвует в числовых или фасетных операциях: артикул поставщика, краткое описание, код маркировки. Важно помнить: строка не сортируется по алфавиту в компонентах без явной настройки и плохо дружит с регистром — «Красный» и «красный» будут разными значениями.

Число берём везде, где нужен диапазонный фильтр: вес, габариты, мощность, гарантийный срок. Хранится оно строкой, но умный фильтр и сортировка приводят его к числу — поэтому в фильтре «от 5 до 10» работает корректно, а вот «5 кг» уже нет: суффикс сломает приведение. Единицы измерения держим отдельным свойством-списком.

Список — основной тип для фасетного фильтра: цвет, материал, сезон, пол. У него есть отдельная таблица значений b_iblock_property_enum, и именно по ней строятся счётчики «сколько товаров в каждой категории». Если пункты могут добавляться контент-менеджером на лету — берите список, а не строку, иначе фильтр превратится в мусор из опечаток.

Привязка к элементам и разделам: когда нужна вместо списка

Привязка к элементам нужна, когда значение свойства — это другой товар, а не абстрактная метка. Классика: «похожие товары», «аксессуары», «комплект», «аналоги». Список тут не подходит — он не умеет тянуть цену, картинку и ссылку связанного элемента. В шаблоне вы получаете PROPERTY_LINKED.ELEMENT с полным набором полей, и это экономит отдельный запрос.

Привязка к разделам решает другую задачу: когда товар нужно показать в нескольких категориях, но дублировать элемент не хочется. Например, «подарки до 3000» — это раздел, а не свойство-список. Компонент catalog.section умеет фильтровать по привязке к разделам, если включить её в настройках. Минус — при большом числе связей растёт нагрузка на выборку: у нас в проектах с активным импортом такие свойства мы индексируем отдельно.

Обе привязки требуют аккуратности при импорте: 1С присылает либо XML_ID, либо название, и маппинг делается вручную — об этом подробнее в секции про интеграцию.

Файл, привязка к HL-блоку и «Справочник»: сложные случаи

Файл — очевидный выбор для PDF-инструкций, сертификатов, чертежей и 3D-моделей. Работает и в одиночном, и в множественном режиме. Учтите: в умном фильтре файловые свойства не участвуют, а при импорте из 1С файлы часто приходят битыми ссылками — проверяйте PROPERTY_VALUE_ID перед выводом.

Привязка к HL-блоку — это когда справочник живёт отдельно от каталога и переиспользуется несколькими инфоблоками. Бренды, регионы доставки, поставщики. Свойство хранит ID записи HL, а саму сущность вы подтягиваете через HighloadBlockTable. Такой подход даёт единую точку правки: поменяли название бренда в HL — обновилось везде.

«Справочник» — отдельный тип, доступный при установленном модуле catalog. Он похож на привязку к HL, но заточен под большие классификаторы (МКТУ, ОКЕИ, коды ТН ВЭД) и умеет подсказки при вводе. В умном фильтре работает, но требует настройки фасетного индекса. Если классификатор небольшой и статичный — берите обычный список, «Справочник» здесь избыточен.

Смена типа свойства после запуска не конвертирует значения. Старые данные остаются в старой таблице в старом формате, а новое свойство читается как пустое. Умный фильтр при этом молча теряет часть товаров — внешне всё работает, но в выдаче пропадают позиции. Если тип всё-таки нужно поменять, заранее готовьте скрипт миграции значений и пересборку фасетного индекса.

Настройка свойств инфоблока через админку: пошагово

Контент-менеджеры чаще всего заводят свойства сами — когда нужно добавить новую характеристику в карточку товара, не дожидаясь разработчика. У нас в практике это регулярная ситуация: маркетинг просит «показывать на карточке страну производителя», и ждать спринт разработки никто не хочет. Хорошая новость в том, что 80% свойств в каталоге — это стандартные поля, которые можно создать за пять минут прямо в админке, не трогая код.

Из всей формы добавления свойства реально важны пять полей: код, название, тип, флаги поведения и — для списочных типов — сами значения списка. Всё остальное (сортировка, подсказки, привязка к разделам) настраивается позже и по потребности. Ниже — порядок действий, который мы показываем новым контент-менеджерам на онбординге.

  1. Открыть в админке Контент → Инфоблоки, выбрать нужный инфоблок каталога, перейти на вкладку «Свойства» и нажать кнопку «Добавить свойство».
  2. Заполнить код (только латиница, snake_case, например country_of_origin), название (человеческое, для формы) и выбрать тип из списка. Код после создания менять нельзя — на нём завязаны шаблоны, фильтры и маппинг из 1С.
  3. Выставить флаги поведения: множественное, обязательное, участвует в умном фильтре, показывать в списке. Каждый флаг потом можно переключить, но продумать заранее дешевле.
  4. Для типа «Список» — заполнить значения списка: каждому задать сортировку и символьный код (он же XML_ID, по нему значения матчатся при импорте из 1С).
  5. Сохранить и проверить результат: открыть любой товар этого инфоблока и убедиться, что новое поле появилось в форме редактирования.

В каталогах на 20k+ SKU у нас сложился свой дефолтный набор флагов. «Показывать в списке» включаем почти всегда — контент-менеджеру удобнее видеть характеристику в общем списке товаров, чем открывать карточку. «Участвует в умном фильтре» — только для свойств, по которым реально будет фильтрация: каждое такое свойство добавляет нагрузку на индекс фильтра, и десяток лишних флагов заметно замедляет страницы каталога.

А вот «обязательное» мы стараемся не ставить на свойства, которые приходят из импорта. Логика простая: если 1С по какой-то причине не передала значение, а свойство помечено обязательным, элемент не сохранится или импорт упадёт с ошибкой валидации. На каталоге в десятки тысяч позиций это превращается в массовый сбой обмена. Обязательность имеет смысл только для полей, которые заполняет человек руками — например, SEO-заголовок или ручная метка «Хит продаж».

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

Настройка свойств через API: CIBlockProperty::Add и Update

Распространённая ошибка — заводить свойства руками, когда их уже больше десятка. Представим типовую ситуацию: каталог разворачивается на новом окружении, шесть инфоблоков, в каждом по 6–8 свойств — цвет, материал, размерная сетка, сезонность, габариты, страна производства. Через админку это час кликов, и почти гарантированно где-то забудется флаг «Множественное» или привязка к разделу. А если между dev и prod нужно синхронизировать конфигурацию — руками это вообще не воспроизводимо.

Мы в таких проектах описываем свойства кодом и запускаем миграцию при развёртывании. CIBlockProperty::Add() и CIBlockProperty::Update() — старый, но живой API модуля iblock, который решает задачу за секунды и оставляет след в репозитории. Комбинируется с CIBlockPropertyEnum::Add() для списочных свойств и с проверкой по CODE, чтобы миграция была идемпотентной — можно гонять её повторно без дублей.

Как добавить свойство через CIBlockProperty::Add

Метод принимает один ассоциативный массив $arFields и возвращает ID созданного свойства или false с записью ошибки в $APPLICATION->GetException(). Обязательных полей немного: IBLOCK_ID, NAME, CODE, PROPERTY_TYPE. Остальное — SORT, MULTIPLE, IS_REQUIRED, USER_TYPE — имеет дефолты, но по опыту их лучше задавать явно: SORT по умолчанию 500, и если не проставить, свойства выстроятся в непредсказуемом порядке в форме редактирования.

Для списочного свойства (PROPERTY_TYPE = 'L') значения хранятся отдельно — их добавляют через CIBlockPropertyEnum::Add(), передавая полученный PROPERTY_ID. Порядок важен: сначала создаём свойство, потом наполняем список. Если поменяете порядок — получите свойство-сироту без значений.

PHP
<?php
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

$iblockId = 17; // замените на ваш IBLOCK_ID

$arFields = [
    'IBLOCK_ID'      => $iblockId,
    'NAME'           => 'Материал',
    'CODE'           => 'MATERIAL',
    'PROPERTY_TYPE'  => 'L',        // список
    'SORT'           => 100,
    'MULTIPLE'       => 'N',
    'IS_REQUIRED'    => 'N',
    'FILTRABLE'      => 'Y',
    'ACTIVE'         => 'Y',
];

$ibp = new CIBlockProperty();
$propId = $ibp->Add($arFields);

if (!$propId) {
    global $APPLICATION;
    throw new \RuntimeException('Не удалось создать свойство: ' . $APPLICATION->GetException()->GetString());
}

foreach (['Хлопок' => 100, 'Лён' => 200, 'Шерсть' => 300] as $value => $sort) {
    CIBlockPropertyEnum::Add([
            'PROPERTY_ID' => $propId,
            'VALUE'       => $value,
            'SORT'        => $sort,
            'DEF'         => 'N',
    ]);
}

Как обновить свойство, не потеряв значения

Обновление опаснее создания: CIBlockProperty::Update($ID, $arFields) перезаписывает переданные поля, а если случайно передать пустой PROPERTY_TYPE — тип сбросится. Значения элементов при этом не удаляются, но для списочных свойств есть нюанс: обновление PROPERTY_TYPE или USER_TYPE может «осиротить» привязки.

Мы всегда идём от CODE, а не от ID — ID на dev и prod разные, а CODE мы задаём сами и он стабилен. Схема: ищем существующее свойство через CIBlockProperty::GetList() с фильтром по IBLOCK_ID и CODE. Если нашли — Update по его ID, если нет — Add. Такой upsert безопасно запускать повторно.

PHP
<?php
$iblockId = 17;
$code     = 'MATERIAL';

$existing = CIBlockProperty::GetList(
    ['SORT' => 'ASC'],
    ['IBLOCK_ID' => $iblockId, 'CODE' => $code]
)->Fetch();

$arFields = [
    'NAME'          => 'Материал (основной)',
    'SORT'          => 150,
    'IS_REQUIRED'   => 'Y',
    'FILTRABLE'     => 'Y',
];

$ibp = new CIBlockProperty();

if ($existing) {
    // не трогаем PROPERTY_TYPE и CODE — только метаданные
    $ok = $ibp->Update($existing['ID'], $arFields);
    $propId = $existing['ID'];
} else {
    $arFields['IBLOCK_ID']     = $iblockId;
    $arFields['CODE']          = $code;
    $arFields['PROPERTY_TYPE'] = 'L';
    $propId = $ibp->Add($arFields);
}

if (!$ok && !$propId) {
    throw new \RuntimeException('Ошибка синхронизации свойства ' . $code);
}

Что ещё стоит держать в голове при настройке свойств инфоблока:

  • Кеш инфоблока. После Add или Update сбрасывайте теги через CIBlock::clearIBlockTagCache($iblockId) — иначе компонент отдаст старую конфигурацию.
  • Нет транзакции. Если упадёт добавление значений через CIBlockPropertyEnum::Add, свойство останется созданным, но пустым — обрабатывайте откат вручную.
  • SORT по умолчанию 500. Все свойства без явного SORT получат одинаковый вес и выстроятся в произвольном порядке.
  • IBLOCK_ID обязателен. Без него Add вернёт false, а GetList выдаст свойства со всех инфоблоков — сначала проверьте, что фильтр не пустой.
  • Компонент не увидит свойство без сброса кеша страницы и кеша компонента — на проде это неочевидный симптом «код отработал, а на сайте пусто».

Вывод свойств инфоблока в шаблоне компонента

Самая частая причина вопроса «почему в шаблоне не видно свойство» — оно туда просто не попало. Если кода свойства в этом списке нет, в шаблоне его тоже нет — сколько бы раз вы ни писали $arResult['PROPERTIES']['MY_CODE'] .

Второй нюанс: даже когда свойство выбрано, у него разная форма значения. Одиночное — строка или число в ['VALUE'], множественное — массив. Свойство типа «Список» хранит в ['VALUE'] ID варианта, а не текст. Ниже — рабочие паттерны для шаблона и для случаев, когда компонент использовать не хочется.

PHP
<?php
// Шаблон компонента bitrix:catalog.element.
// Свойства должны быть указаны в PROPERTY_CODE настроек компонента,
// иначе в $arResult['PROPERTIES'] их не будет.

$props = $arResult['PROPERTIES'];

// Одиночное свойство, например артикул производителя
if (!empty($props['ARTNUMBER']['VALUE'])) {
    echo '<span class="artnumber">'
    . htmlspecialcharsbx($props['ARTNUMBER']['VALUE'])
    . '</span>';
}

// Множественное свойство, например галерея дополнительных изображений
if (!empty($props['GALLERY']['VALUE'])) {
    echo '<ul class="gallery-extra">';
    foreach ($props['GALLERY']['VALUE'] as $fileId) {
        $img = CFile::ResizeImageGet(
            $fileId,
            ['width' => 600, 'height' => 600],
            BX_RESIZE_IMAGE_PROPORTIONAL,
            true
        );
        echo '<li><img src="' . $img['src'] . '" alt=""></li>';
    }
    echo '</ul>';
}

Как вывести значение списка, а не его ID

Свойство типа «Список» в ['VALUE'] отдаёт числовой ID варианта — например, 12 вместо «Красный». Компонент уже подставляет расшифровку в ['VALUE_ENUM'], если свойство попало в выборку. Для одиночного списка этого достаточно, для множественного — VALUE_ENUM тоже массив, и порядок совпадает с VALUE.

Если VALUE_ENUM пуст (бывает у свойств, подтянутых вручную, без флага GET_ENUM), берём значения через CIBlockPropertyEnum::GetList и строим карту ID → значение. Такой подход удобен, когда выводите сразу несколько списковых свойств и не хотите дёргать БД на каждый ID.

PHP
<?php
// 1. Быстрый путь: компонент уже отдал расшифровку
$colorEnum = $arResult['PROPERTIES']['COLOR']['VALUE_ENUM'] ?? null;
if ($colorEnum !== null) {
    echo htmlspecialcharsbx($colorEnum);
}

// 2. Универсальный путь: собираем карту значений списка
$enumMap = [];
$rsEnum = CIBlockPropertyEnum::GetList(
    ['SORT' => 'ASC'],
    ['IBLOCK_ID' => $arResult['IBLOCK_ID'], 'CODE' => 'COLOR']
);
while ($enum = $rsEnum->Fetch()) {
    $enumMap[$enum['ID']] = $enum['VALUE'];
}

// Множественное списковое свойство
if (!empty($arResult['PROPERTIES']['COLOR']['VALUE'])) {
    $names = [];
    foreach ((array)$arResult['PROPERTIES']['COLOR']['VALUE'] as $id) {
        if (isset($enumMap[$id])) {
            $names[] = $enumMap[$id];
        }
    }
    echo implode(', ', array_map('htmlspecialcharsbx', $names));
}

Как вывести свойства через CIBlockElement::GetProperty вне компонента

Когда свойства нужны не в шаблоне, а в своём скрипте — в ajax.php, в письме, в REST-ручке — компонент не нужен. Хватает CIBlockElement::GetProperty: передаёте ID инфоблока, ID элемента и фильтр по коду свойства. Для множественных свойств результат приходит несколькими строками, и их нужно аккуратно склеить.

PHP
<?php
$elementId = 12345;
$iblockId  = 7; // замените на ваш IBLOCK_ID

$rsProp = CIBlockElement::GetProperty(
    $iblockId,
    $elementId,
    ['SORT' => 'ASC'],
    ['CODE' => 'MATERIAL'] // фильтр по коду свойства
);

$materials = [];
while ($prop = $rsProp->Fetch()) {
    if ($prop['VALUE'] === '' || $prop['VALUE'] === false) {
        continue;
    }
    // Для списков берём текст, для строк — значение
    $materials[] = $prop['VALUE_ENUM'] ?: $prop['VALUE'];
}

if ($materials) {
    echo 'Материалы: ' . implode(', ', array_map('htmlspecialcharsbx', $materials));
}

print_r показывает свойства, а в проде пусто. Почти всегда виноват кеш компонента: правки в шаблоне или в настройках PROPERTY_CODE не подхватились, потому что кеш ещё жив. Во время разработки снимите галочку кеширования в настройках компонента или добавьте $arParams['CACHE_TYPE'] = 'N' в вызов. На проде — либо ждите истечения TTL, либо сбрасывайте кеш точечно через CBitrixComponent::clearComponentCache() или админку. И помните: если свойство не в списке PROPERTY_CODE, никакой сброс кеша его не покажет — сначала добавьте код, потом чистите.

Свойства инфоблока и интеграция с 1С: маппинг и подводные камни

В обмене с 1С свойства товара приходят не по символьному коду и не по названию, а по XML_ID — это уникальный идентификатор, который 1С хранит у себя в справочнике характеристик. Bitrix при импорте ищет свойство в инфоблоке именно по этому XML_ID, и если совпадения нет — значение просто не записывается. Никакой ошибки в логе, никакого предупреждения: товар обновился, а характеристика осталась пустой или сохранила старое значение.

В проектах с активным обменом мы регулярно видим эту картину. Контент-менеджеры жалуются, что «цвет пропал у половины каталога», а на деле разошёлся маппинг между справочником 1С и свойствами инфоблока. Проблема коварна тем, что проявляется не сразу: пока XML_ID совпадают, всё работает, и никто не задумывается о связи. Ломается она в момент, когда в 1С переименовали характеристику, добавили новую или пересобрали конфигурацию — и обмен молча теряет данные. Дальше разберём, как этот маппинг устроен, где его проверять и как подстелить соломки, чтобы импорт не затирал значения втихую.

Как сопоставить свойства 1С и Битрикс по XML_ID

Связь между свойством 1С и свойством инфоблока держится на одном поле — XML_ID в таблице b_iblock_property. Когда 1С присылает в XML узел характеристики, CommerceML-обработчик Битрикса ищет свойство с таким же XML_ID и пишет в него значение. Если не нашёл — создаёт новое (при включённой опции) либо игнорирует. Поэтому первое, что мы делаем при разборе «пропавших» характеристик, — выгружаем список свойств инфоблока с их XML_ID и сверяем с тем, что реально приходит в XML от 1С.

Сверить можно двумя способами: глазами через админку (в списке свойств XML_ID не виден — нужно открывать каждое) или скриптом. Второй вариант быстрее и не врёт. Типовой диагностический скрипт выгружает свойства инфоблока и сохраняет карту «XML_ID → CODE», чтобы потом сравнить её с выгрузкой из 1С.

Дальше — контроль на лету. Регистрируем обработчик на OnBeforeIBlockElementUpdate, который перед записью элемента проверяет: все ли XML_ID из пришедших свойств существуют в инфоблоке. Если нет — пишем в лог, а не молчим.

PHP
<?php
// init.php — контроль маппинга свойств при импорте из 1С
use Bitrix\Main\EventManager;
use Bitrix\Main\Diag\Debug;

EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockElementUpdate',
    'checkPropertyMapping'
);

function checkPropertyMapping(&$arFields)
{
    $iblockId = (int)$arFields['IBLOCK_ID'];
    // замените на ваш IBLOCK_ID каталога
    if ($iblockId !== CATALOG_IBLOCK_ID) {
        return true;
    }

    // карта XML_ID -> CODE для свойств инфоблока
    $knownXmlIds = [];
    $rs = \CIBlockProperty::GetList(
        ['ID' => 'ASC'],
        ['IBLOCK_ID' => $iblockId]
    );
    while ($prop = $rs->Fetch()) {
        if ($prop['XML_ID'] !== '') {
            $knownXmlIds[$prop['XML_ID']] = $prop['CODE'];
        }
    }

    // свойства, пришедшие в обмене (упрощённо: из PROPERTY_VALUES)
    $incoming = $arFields['PROPERTY_VALUES'] ?? [];
    foreach ($incoming as $propId => $values) {
        $rsProp = \CIBlockProperty::GetByID($propId);
        $prop = $rsProp->Fetch();
        if (!$prop) {
            continue;
        }
        $xmlId = $prop['XML_ID'];
        if ($xmlId !== '' && !isset($knownXmlIds[$xmlId])) {
            Debug::writeToFile(
                ['PROPERTY_ID' => $propId, 'XML_ID' => $xmlId, 'ELEMENT' => $arFields['ID']],
                '1c-mapping-mismatch',
                '/local/logs/1c-mapping.log'
            );
        }
    }

    return true;
}

Что делать, если 1С присылает свойство, которого нет в инфоблоке

Ситуация типовая: поставщик добавил в справочник новую характеристику, обмен пошёл, а свойства с таким XML_ID в инфоблоке нет. По умолчанию Битрикс либо создаст его автоматически (если в настройках обмена разрешено создание свойств), либо просто пропустит значение. Первый вариант опасен тем, что свойство создастся с случайным кодом и типом, и потом его придётся переделывать руками. Второй — тихой потерей данных.

Мы придерживаемся такого подхода: новые свойства из 1С не создавать автоматически, а складывать в отдельный лог и разбирать пачкой раз в неделю. Разработчик или контент-менеджер заводит свойство в инфоблоке вручную, задаёт корректный код, тип и XML_ID, и следующий обмен уже пишет в него значения. Так каталог не зарастает мусорными свойствами вроде «Характеристика_1С_12345».

Если поток новых характеристик большой и ручной разбор не тянет — делаем полуавтомат: обработчик читает XML, сравнивает с инфоблоком и присылает на почту отчёт «вот эти XML_ID не замаплены». Дальше решение всё равно за человеком.

Свойство в 1С Свойство в Битрикс Симптом Решение
Новая характеристика Отсутствует Значение не записалось Завести свойство с тем же XML_ID
XML_ID изменён Старый XML_ID Значения затираются пустым Обновить XML_ID свойства в инфоблоке
Тип «Справочник» Тип «Строка» Значение пишется как ID, не как текст Сменить тип свойства на список
Множественное свойство Одиночное Сохраняется только первое значение Включить «Множественное»
Свойство удалено в 1С Свойство живо в Битрикс Старые значения висят мёртвым грузом Деактивировать или удалить свойство

Свойства, которые 1С не должна трогать: SEO-тексты, теги, ручные метки

Самая частая жалоба от контент-менеджеров в проектах с активным обменом: «вчера всё заполнила, сегодня прихожу — описания пустые». Открываем лог импорта, смотрим, что происходило ночью. Ночной обмен из 1С прошёл, и вместе с ценами и остатками он перезаписал SEO_DESCRIPTION, SEO_TITLE, SEO_KEYWORDS и пару свойств, которые менеджеры заполняли руками неделями. В каталоге на 20 000+ SKU это превращается в регулярный откат работы целого отдела.

Причина всегда одна и та же: обмен не отличает «1С не передала значение» от «1С передала пустое значение». В XML-выгрузке тег отсутствует — модуль импорта трактует это как «очистить поле» и затирает то, что было. То же самое происходит с ручными метками вроде «Хит», «Новинка», «Снято с производства» — если эти статусы живут в свойствах инфоблока, а не в отдельном справочнике.

У нас на проектах это решается на уровне события OnBeforeIBlockElementUpdate: перехватываем обновление элемента, сверяем пришедшие значения с резервной таблицей и возвращаем защищённые свойства, если обмен прислал пустоту.

Ниже — рабочий обработчик и последовательность внедрения.

PHP
<?php

use Bitrix\Main\EventManager;
use Bitrix\Main\Application;
use Bitrix\Main\Loader;

Loader::includeModule('iblock');

// Список инфоблоков и защищённых свойств
const PROTECTED_MAP = [
    15 => ['SEO_TITLE', 'SEO_DESCRIPTION', 'SEO_KEYWORDS', 'MANUAL_TAG'],
    16 => ['SEO_TITLE', 'SEO_DESCRIPTION', 'MANUAL_TAG'],
];

EventManager::getInstance()->addEventHandler(
    'iblock',
    'OnBeforeIBlockElementUpdate',
    ['ProtectedPropsHandler', 'onBeforeUpdate']
);

class ProtectedPropsHandler
{
    public static function onBeforeUpdate(&$arFields): bool
    {
        $iblockId = (int)($arFields['IBLOCK_ID'] ?? 0);
        if (!isset(PROTECTED_MAP[$iblockId]) || empty($arFields['ID'])) {
            return true;
        }

        $elementId   = (int)$arFields['ID'];
        $protected   = PROTECTED_MAP[$iblockId];
        $connection  = Application::getConnection();
        $sqlHelper   = $connection->getSqlHelper();

        // Значения, которые пришли из импорта (могут быть пустыми)
        $incoming = $arFields['PROPERTY_VALUES'] ?? [];

        // Резервная таблица: element_id, property_code, value
        $rows = $connection->query(
            "SELECT PROPERTY_CODE, VALUE FROM b_protected_props
            WHERE ELEMENT_ID = {$elementId}"
        )->fetchAll();

        $backup = [];
        foreach ($rows as $row) {
            $backup[$row['PROPERTY_CODE']] = $row['VALUE'];
        }

        foreach ($protected as $code) {
            $newValue = $incoming[$code] ?? null;
            $isEmpty  = ($newValue === null || $newValue === '' || $newValue === false);

            if ($isEmpty && isset($backup[$code])) {
                // Возвращаем сохранённое значение
                $arFields['PROPERTY_VALUES'][$code] = $backup[$code];
            } elseif (!$isEmpty && $newValue !== ($backup[$code] ?? null)) {
                // Значение изменилось не через импорт — обновляем бэкап
                $connection->queryExecute(
                    "REPLACE INTO b_protected_props (ELEMENT_ID, PROPERTY_CODE, VALUE)
                    VALUES ({$elementId}, '{$sqlHelper->forSql($code)}',
                        '{$sqlHelper->forSql($newValue)}')"
                );
            }
        }

        return true;
    }
}

Порядок внедрения в проекте выглядит так:

  1. Определить список защищённых свойств — только те, что реально заполняются вручную, а не приходят из 1С.
  2. Завести таблицу бэкапа b_protected_props с полями ELEMENT_ID, PROPERTY_CODE, VALUE и индексом по элементу.
  3. Повесить обработчик на OnBeforeIBlockElementUpdate через EventManager в init.php или в своём модуле.
  4. Логировать каждое расхождение — когда импорт прислал пусто, а мы вернули значение из бэкапа, писать строку в /local/logs/protected_props.log.

Самые частые ошибки при работе со свойствами инфоблока

Распространённая ошибка: считать, что свойства инфоблока — это «просто поля, которые заполняются в карточке». На деле это отдельный слой со своей типизацией, кешем, поведением при обмене и своей логикой в шаблоне. Мы собрали грабли, которые повторяются в проектах чаще всего — от смены типа свойства без конвертации до правки ядра вместо обработчика. Почти все они выглядят как «странный баг Битрикса», хотя это следствие одного неверного шага, сделанного раньше. Дальше — короткая сводка, а потом разберём, какие ошибки ловятся на этапе разработки, а какие всплывают только в проде через недели.

Грабля Причина Как чинить
Смена типа без конвертации Значения в БД остались в старом формате Перезаписать значения через API или импорт
Нет PROPERTY_CODE в компоненте Свойство не попало в выборку $arResult Добавить код в параметры catalog.section
Чтение VALUE вместо VALUE_ENUM Для списков VALUE — это ID, а не текст Использовать VALUE_ENUM или GetEnumList
Правка ядра вместо обработчика Хочется «быстро поправить» вывод свойства Событие OnAfterIBlockElementUpdate / шаблон
Нет сброса кеша инфоблока Изменения в БД не видны на фронте CIBlock::clearIBlockTagCache() или ClearCache

Часть ошибок вылезает сразу. Нет PROPERTY_CODE в параметрах компонента — свойство просто не появляется в $arResult['PROPERTIES'], и это видно на первой же отладке. То же с чтением VALUE вместо VALUE_ENUM для списочных свойств: вместо текста выводится числовой ID. Такие вещи отсеиваются на этапе разработки, если разработчик хоть раз открывает var_dump или xDebug.

А вот смена типа свойства и отсутствие сброса кеша коварнее. Смена типа без конвертации может не проявиться неделями: пока контент-менеджер не откроет карточку и не увидит пустое поле вместо значения. Данные в БД формально на месте, но в новом формате они нечитаемы, и «пропадают» ровно в тот момент, когда кто-то идёт их править.

Кеш — та же история: ClearCache при обновлении элемента через API не всегда срабатывает автоматически. Правка, сделанная скриптом, появится на фронте только после ручного сброса или истечения TTL. В проде это выглядит как «данные не сохраняются», хотя в админке всё на месте.

Отдельная категория — правка ядра. Мы регулярно видим проекты, где логика вывода свойства вшита прямо в /bitrix/modules/iblock/: это работает до первого обновления платформы. Правильный путь — обработчик события или кастомный шаблон, как мы разбирали в секции про вывод свойств.

Читайте также: Инфоблоки Битрикс: создание, вывод элементов, разделы и свойства, Как ускорить Битрикс: композит, кэширование и настройка сервера.

Подробнее об услуге: разработка корпоративных сайтов на Битрикс.

Частые вопросы

Можно ли задать свойству значение по умолчанию, которое подставится при создании элемента?

Да, у свойства есть поле DEFAULT_VALUE, но оно работает только для типов S (строка), N (число) и L (список) и подставляется лишь в форме редактирования элемента в админке. При добавлении через CIBlockElement::Add значение по умолчанию не применяется — его нужно передавать в массиве PROPERTY_VALUES явно.

Чем отличается свойство типа L (список) от свойства типа E (привязка к элементам)?

L хранит собственные значения в таблице b_iblock_property_enum и не зависит от других инфоблоков, а E ссылается на ID элементов другого инфоблока через LINK_IBLOCK_ID. Для E при удалении связанного элемента значение свойства остаётся «висячим», поэтому связи нужно чистить вручную или через агент.

Что делать, если после обновления через CIBlockProperty::Update значения свойства у элементов пропали?

Скорее всего вы сменили PROPERTY_TYPE или USER_TYPE — при этом старые значения в b_iblock_element_property становятся несовместимыми и перестают читаться. Перед сменой типа сделайте дамп таблицы и перенесите данные вручную, либо создайте новое свойство и скопируйте значения через CIBlockElement::SetPropertyValuesEx.

А если у меня свойство с множественностью (MULTIPLE=Y), как правильно вывести его в шаблоне без дублей?

В $arResult["PROPERTIES"]["CODE"]["VALUE"] будет массив, а не строка, поэтому используйте foreach и не забудьте про ключ ~VALUE для необработанного значения. Если нужно вывести через компонент, проверяйте, что в параметрах указан нужный код в PROPERTY_CODE, иначе массив будет пустым.

Можно ли запретить 1С перезаписывать конкретное свойство при обмене?

Да, в настройках свойства снимите галку «Участвует в обмене с 1С» или в XML-выгрузке не передавайте тег <Свойство> с этим кодом. Дополнительно можно повесить обработчик на событие OnBeforeIBlockElementUpdate и восстанавливать значение из резервной копии, если оно изменилось.

Чем отличается хранение значений в b_iblock_element_property от b_iblock_element_prop_sN?

Первая таблица используется для множественных свойств и свойств с типом F/E/G, вторая — для одиночных значений, где N — это ID инфоблока. Прямые запросы к prop_sN быстрее, но ломаются при смене ID инфоблока, поэтому в коде лучше работать через CIBlockElement::GetProperty.

#Bitrix #PHP #Bitrix модули
автор · Backend / SRE Engineer
Артем Колячек

Backend-разработчик и SRE в студии Paradigma. Занимается тем, что у других проектов обычно обнаруживается за неделю до запуска: переездом Bitrix-проектов между серверами, починкой кешей после миграций, выстраиванием pipeline для контейнеров, мониторингом под нагрузкой.

До Paradigma — 7 лет в backend (PHP + Postgres) и в operations (Linux, Docker, Coolify, restic-бэкапы). Любит когда логи разговаривают полным синтаксисом ошибки, а не «что-то пошло не так».

На блоге пишет ровно про те ситуации с которыми сам разбирался руками: какой Bitrix-апдейт сломал кеш и как откатить, почему Docker-сеть не находит контейнер после рекрейта, что делать когда asyncpg ругается на event loop.