"Similar Products" Block Development for 1C-Bitrix
"Similar products" is a block for cases when the current product doesn't suit the buyer: different color, different size, different manufacturer. Or out of stock. The block's purpose is to keep the user in the catalog and offer an alternative, rather than sending them to a competitor's site.
Similarity Criteria
"Similar" is a concept that needs to be defined for each project. Possible criteria:
- Same category — the basic level.
- Same characteristics — for technical products (electronics, building materials): power, material, type.
- Similar price — a buyer with a budget rarely moves to a different price tier.
- Same brand — works for brand-dependent purchases (cosmetics, clothing).
- Similar tags — if the catalog has a tag structure.
The similarity system is built as a weighted sum of matches across the chosen criteria.
Similarity Score Algorithm
function calculateSimilarityScore(int $productId, int $candidateId): float {
$product = getProductData($productId);
$candidate = getProductData($candidateId);
$score = 0.0;
// Same category (+30 points)
if ($candidate['IBLOCK_SECTION_ID'] === $product['IBLOCK_SECTION_ID']) {
$score += 30;
}
// Similar price (±20% → +20 points, ±40% → +10 points)
$priceDiff = abs($candidate['PRICE'] - $product['PRICE']) / max($product['PRICE'], 1);
if ($priceDiff <= 0.2) $score += 20;
elseif ($priceDiff <= 0.4) $score += 10;
// Brand match (+25 points)
if ($candidate['PROP_BRAND'] && $candidate['PROP_BRAND'] === $product['PROP_BRAND']) {
$score += 25;
}
// Characteristic matches (up to +25 points)
$specKeys = ['PROP_MATERIAL', 'PROP_COLOR', 'PROP_SIZE_TYPE'];
$specScore = 0;
foreach ($specKeys as $key) {
if (isset($product[$key], $candidate[$key]) && $product[$key] === $candidate[$key]) {
$specScore += 8;
}
}
$score += min($specScore, 25);
return $score;
}
Pre-Calculation for Catalogs with Thousands of Products
Calculating similarity on the fly for a catalog of 10,000+ products is not feasible. Pre-calculation:
CREATE TABLE custom_similar_products (
product_id INT NOT NULL,
similar_id INT NOT NULL,
score FLOAT NOT NULL,
calculated_at DATETIME DEFAULT NOW(),
PRIMARY KEY (product_id, similar_id),
INDEX idx_product (product_id, score DESC)
);
The agent calculates similarity within each category:
function RecalcSimilarProductsAgent(): string {
static $sectionOffset = 0;
$sections = getSectionsBatch($sectionOffset, 10);
if (empty($sections)) {
$sectionOffset = 0; // start over on next run
return 'RecalcSimilarProductsAgent();';
}
foreach ($sections as $section) {
$products = getProductsBySection($section['ID']);
foreach ($products as $p) {
$scores = [];
foreach ($products as $candidate) {
if ($candidate['ID'] === $p['ID']) continue;
$scores[$candidate['ID']] = calculateSimilarityScore($p['ID'], $candidate['ID']);
}
arsort($scores);
$top = array_slice($scores, 0, 20, true);
saveSimilarProducts($p['ID'], $top);
}
}
$sectionOffset += 10;
return 'RecalcSimilarProductsAgent();';
}
The agent runs every night and processes 10 sections per run.
Component with Stock Awareness
Before displaying, filter out products with no stock:
$similarIds = getSimilarFromTable($productId, 20); // extra margin for filtering
$filter = [
'ID' => $similarIds,
'ACTIVE' => 'Y',
'!ID' => $productId,
];
// If configured: show only in-stock products
if ($arParams['ONLY_AVAILABLE'] === 'Y') {
$filter['>CATALOG_QUANTITY'] = 0;
}
$res = \CIBlockElement::GetList(
[],
$filter,
false,
['nPageSize' => (int)$arParams['LIMIT']],
['ID', 'NAME', 'DETAIL_PAGE_URL', 'PREVIEW_PICTURE', 'CATALOG_PRICE_1']
);
Manual Similarity Management
For cases where the algorithm falls short (specific products, non-standard relationships) — add a manual management tool in the admin area. A manager opens a product → "Similar products" tab → multi-select from the catalog. Manual links take priority over automatic ones.
-- Manual links stored separately, not overwritten by the agent
CREATE TABLE custom_similar_manual (
product_id INT NOT NULL,
similar_id INT NOT NULL,
sort INT DEFAULT 500,
created_by INT,
created_at DATETIME DEFAULT NOW(),
PRIMARY KEY (product_id, similar_id)
);
Display Logic
Final selection for the block: first manual links (ordered by sort), then automatic ones by score. If the combined total is less than required — fallback to products from the same category.
Difference from "Frequently Bought Together"
| Criterion | Similar products | Bought together |
|---|---|---|
| Basis | Product properties | Order history |
| New products | Works immediately | Requires purchase history |
| Logic | Alternative | Complement |
| Location on site | Product page | Product page, cart |
| Impact on | User retention | Average order value |
Timeline
| Stage | Timeline |
|---|---|
| Defining similarity criteria for the catalog | 1 day |
| Score calculation algorithm + table | 2–3 days |
| Pre-calculation agent | 1–2 days |
| Component with filtering and fallback | 2–3 days |
| Manual management in admin panel | 1–2 days |
| Testing | 1–2 days |
Total: 1–1.5 weeks.







