Setting up dynamic blocks based on 1C-Bitrix viewing history

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
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • 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
    565
  • 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
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

Dynamic Blocks Setup Based on View History in 1C-Bitrix

The "You viewed" block is one of the most converting elements on a product card. A user returned from search, didn't find what they wanted, but sees their view history — and often returns to a product they already considered. Bitrix has no built-in component for this. The "Personalization" module exists in Enterprise version, but most projects need to build it from scratch.

Cookies vs. Session vs. Database

Three options for storing view history — with different trade-offs.

Cookies — simplest: array of product IDs serialized in a cookie. Works without a database, requires no authorization, lives until cookie expires. Limitation: maximum 4KB, about 200–300 product IDs. Enough for view history but not complex analytics.

Databaseb_user_behavior table (from previous task) or separate b_viewed_products. Works for authorized users, survives device changes, allows pattern analysis. For anons — session binding with subsequent merge.

Optimal approach: cookies for instant display (no database query), background write to database for analytics.

Writing to Cookie on Product View

In the catalog.element template or component, add JavaScript:

(function() {
    var elementId = <?= (int)$arResult['ID'] ?>;
    var cookieName = 'bx_viewed';
    var maxItems = 20;

    var viewed = [];
    try {
        var raw = document.cookie.match(/bx_viewed=([^;]+)/);
        if (raw) viewed = JSON.parse(decodeURIComponent(raw[1]));
    } catch(e) {}

    viewed = viewed.filter(function(id) { return id !== elementId; });
    viewed.unshift(elementId);
    if (viewed.length > maxItems) viewed = viewed.slice(0, maxItems);

    var expires = new Date(Date.now() + 30 * 24 * 3600 * 1000).toUTCString();
    document.cookie = cookieName + '=' + encodeURIComponent(JSON.stringify(viewed))
        + '; expires=' + expires + '; path=/; SameSite=Lax';
})();

View History Display Component

Custom component local:catalog.viewed reads cookie on server and loads data by ID:

// component.php
$viewed = [];
if (!empty($_COOKIE['bx_viewed'])) {
    $raw = json_decode($_COOKIE['bx_viewed'], true);
    if (is_array($raw)) {
        $viewed = array_map('intval', array_slice($raw, 0, 10));
    }
}

if (empty($viewed)) {
    $this->AbortResultCache();
    return;
}

$res = \CIBlockElement::GetList(
    [],
    ['ID' => $viewed, 'IBLOCK_ID' => CATALOG_IBLOCK_ID, 'ACTIVE' => 'Y'],
    false,
    ['nTopCount' => 10],
    ['ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PAGE_URL', 'CATALOG_PRICE_1']
);

$items = [];
while ($el = $res->GetNextElement()) {
    $fields = $el->GetFields();
    $items[$fields['ID']] = $fields;
}

// Restore view order
$arResult = array_filter(array_map(fn($id) => $items[$id] ?? null, $viewed));

Component should work without cache (CACHE_TYPE = 'N') or with user/session-bound cache.

Excluding Current Product

If "You viewed" block is placed on a product card — the current product should not appear. Filter:

$currentId = (int)$GLOBALS['arParams']['ELEMENT_ID'] ?? 0;
$viewed = array_filter($viewed, fn($id) => $id !== $currentId);

AJAX Loading to Preserve Page Cache

If the product card is cached (and it should be), the "You viewed" block is better loaded via a separate AJAX request after main content rendering. This allows caching the card at nginx level (or Bitrix cache) while showing personalized content to each user. Controller returns ready HTML fragment — minimal client logic.