Setting up mass binding of 1C-Bitrix trade proposals

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
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1181
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    813
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Bulk Trade Offer Linking Configuration in 1C-Bitrix

Trade offers in Bitrix are elements of a separate iblock, each linked to a parent product via the CML2_LINK field (stored in b_iblock_element_property). When you have thousands of SKUs and this link is broken — offers do not appear on the product page, prices and stock levels are not shown. The need for bulk linking arises during migration from another platform, imports from 1C with errors, or manual database creation.

How the Product–Trade Offer Relationship Works

The catalog iblock contains the main products. A linked iblock is created for trade offers — its ID is specified in the OFFERS_IBLOCK_ID field in b_iblock.ID. The relationship is declared via b_iblock_fields or through the interface: Iblocks → Iblock Types → Trade Catalog → Edit.

Each trade offer has a property with the code CML2_LINK (type "Element link") — the value in b_iblock_element_property.VALUE corresponds to the ID of the parent product.

-- Check how many offers have no link set:
SELECT COUNT(*)
FROM b_iblock_element ie
WHERE ie.IBLOCK_ID = {offers_iblock_id}
AND NOT EXISTS (
    SELECT 1 FROM b_iblock_element_property iep
    INNER JOIN b_iblock_property ip ON ip.ID = iep.IBLOCK_PROPERTY_ID
    WHERE iep.IBLOCK_ELEMENT_ID = ie.ID
    AND ip.CODE = 'CML2_LINK'
    AND iep.VALUE IS NOT NULL
);

Finding the CML2_LINK Property ID

$offersIblockId = 11; // Offers iblock ID

$propRes = \Bitrix\Iblock\PropertyTable::getList([
    'filter' => ['IBLOCK_ID' => $offersIblockId, 'CODE' => 'CML2_LINK'],
    'select' => ['ID'],
])->fetch();

$linkPropertyId = $propRes['ID'];

Bulk Linking by XML_ID

The most common scenario: products and offers have the 1C article stored in b_iblock_element.XML_ID. Linking is done by matching the article or via a mapping table.

// Example: linking offers to products by the first 8 characters of XML_ID
// Product: XML_ID = 'PROD-001', Offers: XML_ID = 'PROD-001-S', 'PROD-001-M', 'PROD-001-L'

$catalogIblockId = 10;
$offersIblockId  = 11;

// Build product map: XML_ID → element ID
$productMap = [];
$productsRes = \CIBlockElement::GetList([], ['IBLOCK_ID' => $catalogIblockId], false, false, ['ID', 'XML_ID']);
while ($row = $productsRes->Fetch()) {
    $productMap[$row['XML_ID']] = $row['ID'];
}

// Process offers in batches
$offersRes = \CIBlockElement::GetList([], ['IBLOCK_ID' => $offersIblockId], false, false, ['ID', 'XML_ID']);

$batch = [];
while ($row = $offersRes->Fetch()) {
    // Determine parent XML_ID: take first N characters or parse by delimiter
    $parentXmlId = substr($row['XML_ID'], 0, 8); // adjust to your scheme
    if (!isset($productMap[$parentXmlId])) continue;

    $parentId = $productMap[$parentXmlId];
    $batch[]  = ['offer_id' => $row['ID'], 'parent_id' => $parentId];

    if (count($batch) >= 200) {
        bindOffersToProducts($batch, $offersIblockId, $linkPropertyId);
        $batch = [];
    }
}
if (!empty($batch)) {
    bindOffersToProducts($batch, $offersIblockId, $linkPropertyId);
}

function bindOffersToProducts(array $batch, int $iblockId, int $propId): void
{
    foreach ($batch as $item) {
        // Check if a record already exists
        $existing = \Bitrix\Iblock\ElementPropertyTable::getList([
            'filter' => [
                'IBLOCK_ELEMENT_ID' => $item['offer_id'],
                'IBLOCK_PROPERTY_ID' => $propId,
            ],
            'select' => ['ID'],
        ])->fetch();

        if ($existing) {
            \Bitrix\Iblock\ElementPropertyTable::update($existing['ID'], [
                'VALUE' => $item['parent_id'],
            ]);
        } else {
            \Bitrix\Iblock\ElementPropertyTable::add([
                'IBLOCK_ELEMENT_ID'  => $item['offer_id'],
                'IBLOCK_PROPERTY_ID' => $propId,
                'VALUE'              => $item['parent_id'],
            ]);
        }
    }
}

Linking via CSV Mapping

If the parent resolution logic is more complex — use an external map:

offer_xml_id,parent_xml_id
SKU-001-RED,PROD-001
SKU-001-BLUE,PROD-001
SKU-002-S,PROD-002
$map = parseCsv('/import/offers_mapping.csv'); // [['offer_xml_id' => ..., 'parent_xml_id' => ...]]
foreach ($map as $row) {
    $offerId  = getElementIdByXmlId($offersIblockId, $row['offer_xml_id']);
    $parentId = getElementIdByXmlId($catalogIblockId, $row['parent_xml_id']);
    if ($offerId && $parentId) {
        \CIBlockElement::SetPropertyValues($offerId, $offersIblockId, $parentId, 'CML2_LINK');
    }
}

Verifying Results and Invalidating Cache

After bulk linking, be sure to:

  1. Check several products in the public-facing site — offers should appear on the product page
  2. Clear the iblock cache for both iblocks
  3. Reindex the facet filter if offers participate in filtering
\Bitrix\Iblock\InformationBlock::cleanTagCache($catalogIblockId);
\Bitrix\Iblock\InformationBlock::cleanTagCache($offersIblockId);
\Bitrix\Iblock\PropertyIndex\Manager::markIblockToReindex($offersIblockId);

Estimated Timelines

Volume Time
Up to 1,000 offers 2–4 hours (analysis + script + verification)
1,000–20,000 offers 1 day
20,000+ offers 2–3 days (including mapping debugging and verification)