Setting up mass changes to product statuses in 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages

Setting Up Bulk Product Status Changes in 1C-Bitrix

Season ended — 200 products need to be unpublished. Promotion launches at midnight — 50 products activate simultaneously. Both scenarios are solved by bulk status changes, but incorrect implementation causes race conditions or crashes MySQL under load.

What is "Status" in Bitrix

A product in Bitrix is an iblock element. "Status" is the ACTIVE field (Y/N) in b_iblock_element. Additionally, there are ACTIVE_FROM and ACTIVE_TO fields — activity periods. If the current time falls outside this range, the element is considered inactive regardless of ACTIVE.

Custom statuses (new item, sale, bestseller) are iblock properties, not the ACTIVE field. Bulk property changes are covered below.

Bulk ACTIVE Changes via D7

Direct ORM update is the fastest method for the ACTIVE field:

$productIds = [1001, 1002, 1003];

foreach (array_chunk($productIds, 100) as $chunk) {
    \Bitrix\Iblock\ElementTable::updateMulti($chunk, ['ACTIVE' => 'N']);
    // Clear cache for updated elements
    foreach ($chunk as $id) {
        \Bitrix\Main\Application::getInstance()->getTaggedCache()
            ->clearByTag('iblock_element_' . $id);
    }
}

updateMulti executes a single UPDATE b_iblock_element SET ACTIVE='N' WHERE ID IN (...) — optimal for MySQL.

However, direct ORM updates bypass Bitrix events (OnBeforeIBlockElementUpdate, OnAfterIBlockElementUpdate). If other modules subscribe to these events (CRM, search, custom handlers), use CIBlockElement::Update():

foreach ($productIds as $id) {
    \CIBlockElement::Update($id, false, ['ACTIVE' => 'N'], false);
    // 4th parameter false — skip permission recalculation
}

Scheduled Deferred Activation

For promotion launches at specific times, use ACTIVE_FROM / ACTIVE_TO fields. No need to run a script at midnight — just set dates:

\CIBlockElement::Update($productId, false, [
    'ACTIVE'      => 'Y',
    'ACTIVE_FROM' => '01.12.2024 00:00:00',
    'ACTIVE_TO'   => '31.12.2024 23:59:59',
]);

Bitrix automatically checks the current date when displaying in the catalog. The filter in the bitrix:catalog.section component by default includes ACTIVE_DATE = 'Y', which verifies the date range.

Bulk Custom Property-Status Changes

A "List" type property (L) — for example, "Status: New / Sale / Bestseller" — changes via SetPropertyValuesEx. For bulk updates:

$enumValues = [];
$enum = \CIBlockPropertyEnum::GetList(
    [],
    ['PROPERTY_ID' => $propertyId, 'VALUE' => 'Sale']
);
if ($enumItem = $enum->Fetch()) {
    $enumValueId = $enumItem['ID'];
}

foreach (array_chunk($productIds, 50) as $chunk) {
    foreach ($chunk as $id) {
        \CIBlockElement::SetPropertyValuesEx($id, $iblockId, [
            'STATUS' => $enumValueId,
        ]);
    }
    usleep(50000);
}

Conditional Status Changes

To change status only for products meeting certain conditions (e.g., deactivate all products with zero stock), retrieve the selection first:

// Find products with zero stock
$zeroStock = \Bitrix\Catalog\ProductTable::getList([
    'filter' => ['QUANTITY' => 0, 'QUANTITY_TRACE' => 'Y'],
    'select' => ['ID', 'IBLOCK_ELEMENT_ID'],
])->fetchAll();

$elementIds = array_column($zeroStock, 'IBLOCK_ELEMENT_ID');

// Deactivate
foreach (array_chunk($elementIds, 100) as $chunk) {
    \Bitrix\Iblock\ElementTable::updateMulti($chunk, ['ACTIVE' => 'N']);
}

This query executes in seconds for thousands of products versus minutes with element-by-element traversal via CIBlockElement::GetList.