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:
- Check several products in the public-facing site — offers should appear on the product page
- Clear the iblock cache for both iblocks
- 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) |







