When in an online store the standard bitrix:catalog.recommended component shows the same products to all visitors, the conversion of the recommendation block drops by 30–60%. The user sees items they already bought or viewed, and leaves for a competitor with an accurate "Customers also bought" block. We, a team with 10+ years of experience and over 50 implementations in 1C-Bitrix, built a personalized recommendation system based on behavior — without ML, only PHP + SQL. Turnkey in 2–5 days. Get a consultation for your project — we'll assess the possibilities for free.
What problems do we solve?
The standard bitrix:catalog.recommended component does not use user behavior. It offers the same to everyone. We implement behavioral signals: views, purchases, cart. Typical mistakes — ignoring event weight (purchase is 10x more important than view) and lack of personal category boost. Our solution fixes these issues, increasing recommendation CTR by 30–60%. In one project with a catalog of 15,000 products, we raised the recommendation block conversion from 1.2% to 3.8% in a week.
How behavior-based recommendations work
We collect events in the b_user_behavior table. Assign weights:
| Event | Weight |
|---|---|
| Product purchase | 10 |
| Add to cart | 5 |
| Add to favorites | 4 |
| Product card view (>30 sec) | 2 |
| Product card view (<30 sec) | 1 |
| Search query with click | 3 |
Weights are stored in configuration — easy to adjust for your business. Analysis window — 60 days (configurable).
Example weight configuration in PHP
$weights = [
'purchase' => 10,
'cart_add' => 5,
'favorite_add' => 4,
'view_long' => 2,
'view_short' => 1,
'search_click' => 3,
];
Why item-based collaborative filtering?
It's the sweet spot between random products and ML. The idea is simple: "those who viewed product A also viewed B, C, D." We compute it directly from the database. Item-based filtering gives 3–5 times more accurate recommendations compared to popular products. It requires no separate ML server and is easily scalable.
Comparison of approaches:
| Approach | Relevance | Complexity | Infrastructure |
|---|---|---|---|
| No personalization | Low | Low | Not required |
| Item-based (ours) | Medium | Medium | PHP + SQL only |
| ML model | High | High | Python + server |
Optimal query to find related products:
SELECT
b2.ENTITY_ID AS recommended_id,
COUNT(DISTINCT b2.USER_ID) AS co_view_count
FROM b_user_behavior b1
JOIN b_user_behavior b2
ON b1.USER_ID = b2.USER_ID
AND b1.ENTITY_ID != b2.ENTITY_ID
AND b2.EVENT_TYPE IN ('view', 'cart_add', 'purchase')
AND b2.DATE_CREATE > NOW() - INTERVAL '60 days'
WHERE
b1.ENTITY_ID = :current_item_id
AND b1.EVENT_TYPE IN ('view', 'cart_add', 'purchase')
GROUP BY b2.ENTITY_ID
ORDER BY co_view_count DESC
LIMIT 20;
This query runs once per hour via a Bitrix agent. We cache the result in a separate table:
CREATE TABLE b_item_recommendations (
ITEM_ID INT NOT NULL,
RECOMMENDED_ID INT NOT NULL,
SCORE FLOAT NOT NULL,
UPDATED_AT TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (ITEM_ID, RECOMMENDED_ID)
);
CREATE INDEX idx_item_recs_item ON b_item_recommendations(ITEM_ID, SCORE DESC);
Personal scoring and candidate selection
From the 20 candidates, we apply a boost based on the user's favorite categories:
function getPersonalizedRecs(int $itemId, int $userId, int $limit = 8): array {
// 1. Get candidates from item-based table
$candidates = getCandidates($itemId, 20);
if (empty($candidates) || !$userId) {
return array_slice($candidates, 0, $limit);
}
// 2. Get categories from user history
$userCategoryIds = getUserTopCategories($userId, 10);
// 3. Boosting: raise products from preferred categories
foreach ($candidates as &$candidate) {
$sectionId = getElementSectionId($candidate['id']);
if (in_array($sectionId, $userCategoryIds)) {
$candidate['score'] *= 1.5;
}
}
// 4. Remove already purchased products
$purchased = getUserPurchasedIds($userId);
$candidates = array_filter($candidates,
fn($c) => !in_array($c['id'], $purchased)
);
usort($candidates, fn($a, $b) => $b['score'] <=> $a['score']);
return array_column(array_slice($candidates, 0, $limit), 'id');
}
Caching and display
Custom component local:catalog.recommendations accepts ELEMENT_ID. The main block is cached by item_id — same candidates for all. Personal boost is applied via a separate AJAX request after page load. This gives high speed: Bitrix-level cache, minimal SQL at render. 1C-Bitrix caching documentation.
Transferring history after authorization
Bitrix does not automatically transfer anonymous behavioral history to an authorized user. We add an OnAfterUserLogin handler:
AddEventHandler('main', 'OnAfterUserLogin', function($fields) {
$fuserId = \CSaleUser::GetAnonymousUserID();
if (!$fuserId) return;
$DB->Query("
UPDATE b_user_behavior SET USER_ID = " . (int)$fields['USER_ID'] . "
WHERE SESSION_ID = '" . $DB->ForSql(session_id()) . "'
AND USER_ID IS NULL
");
});
Monitoring recommendation effectiveness
Key metric: recommendation block CTR — ratio of clicks to impressions. Baseline without personalization: 0.5–1.5%. After switching to item-based with personal boost: 2.5–4.0%. Track via purchase events and a custom click counter in localStorage or through Yandex.Metrica goals.
Additional metrics: conversion of recommended product to purchase, average order value for orders with recommendations vs. without, user return rate with repeated clicks on the block. These data help regularly adjust event weights and the analysis window to the specifics of a particular catalog.
Every quarter we review the weight configuration: if the share of short views grows, reduce their weight. If purchases concentrate in a narrow category, add seasonal boosting by product flags.
What's included in the work
- Audit of current catalog and behavioral data sources.
- Design of
b_user_behaviorschema and weight configuration. - Development of SQL queries for item-based filtering and PHP logic for personal boost.
- Integration of recommendation component and AJAX handler.
- Testing on real users with CTR measurement.
- Documentation on weight configuration and support.
- Training your team to work with the system.
- 30-day warranty for correct operation after delivery.
Timelines and guarantees
Estimated timeline: 2 to 5 business days depending on catalog complexity. Time savings compared to developing from scratch: up to 40%. We provide a warranty for correct operation of recommendations for 30 days after delivery. Order a free audit of your catalog — we'll assess the possibilities.







