Refinement of standard components via component_epilog 1C-Bitrix

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
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • 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

Customizing Standard Components via component_epilog in 1C-Bitrix

component_epilog.php — a file in the component template folder that executes after the component has fully finished, including the template render. Unlike result_modifier.php, at this stage you can no longer affect the HTML output of the current component — but you can execute code with side effects without disrupting caching logic.

Position in the execution sequence

1. component.php (logic, populates $arResult)
2. result_modifier.php (modifies $arResult, before render)
3. template.php (renders HTML)
4. component_epilog.php (after render — actions, JS, analytics)

The file is located in the component template folder:

/local/templates/{site_template}/components/bitrix/catalog.element/default/component_epilog.php

How component_epilog differs from result_modifier

result_modifier component_epilog
When it executes Before template render After template render
Access to $arResult Yes, for modification Yes, read-only
Affects component HTML Yes (via $arResult) No
Executes when cached No (data is taken from cache) Yes, always
For JS and analytics No Yes
For side effects No Yes

Key difference: component_epilog.php always executes, even when the component serves its result from cache. This makes it the ideal location for code that must run on every request — regardless of cache state.

Practical applications

Outputting JS data for analytics (GA4 / Yandex.Metrica):

Product data for analytics is needed on every page view, even when the component is cached:

// component_epilog.php for bitrix:catalog.element
if (!empty($arResult['ID'])) {
    $price    = $arResult['CATALOG_PRICE_1'] ?? 0;
    $name     = $arResult['NAME'] ?? '';
    $category = $arResult['SECTION']['NAME'] ?? '';

    $productJson = json_encode([
        'id'       => $arResult['ID'],
        'name'     => $name,
        'price'    => $price,
        'category' => $category,
    ], JSON_UNESCAPED_UNICODE);

    // Output JS directly from epilog — or inject into <head>
    global $APPLICATION;
    $APPLICATION->AddHeadScript('');
    ?>
    <script>
    window.pageProduct = <?= $productJson ?>;
    gtag('event', 'view_item', {
        currency: 'RUB',
        value: <?= $price ?>,
        items: [{ item_id: '<?= $arResult['ID'] ?>', item_name: <?= json_encode($name) ?>, price: <?= $price ?> }]
    });
    ym(METRIKA_ID, 'reachGoal', 'product_view', { product_id: <?= $arResult['ID'] ?> });
    </script>
    <?php
}

Registering a product view in an analytics system:

Updating a view counter in a custom table on every page view — without breaking the component cache:

// component_epilog.php for bitrix:catalog.element
if (!empty($arResult['ID'])) {
    // Skip bots
    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
    if (preg_match('/bot|crawler|spider|crawling/i', $userAgent)) return;

    // Increment counter (non-blocking — via queue)
    ViewCounterQueue::increment($arResult['ID']);
}

ViewCounterQueue::increment() writes to Redis or a custom table — an agent periodically flushes accumulated views to the main table via a batch UPDATE.

Related products: appending after the main content renders:

If the "similar products" block is heavy and should not be included in the main component's cache:

// component_epilog.php for bitrix:catalog.element
if (!empty($arResult['ID'])) {
    // Component with its own cache — independent of the parent's cache
    $APPLICATION->IncludeComponent(
        'bitrix:catalog.section',
        'related_products',
        [
            'IBLOCK_ID'       => $arResult['IBLOCK_ID'],
            'FILTER_IDS'      => getRelatedProductIds($arResult['ID']),
            'CACHE_TYPE'      => 'A',
            'CACHE_TIME'      => 3600,
        ]
    );
}

Logging events without blocking the main request:

// component_epilog.php for bitrix:sale.basket.basket
if (!empty($arResult['ITEMS'])) {
    $basketValue = array_sum(array_column($arResult['ITEMS'], 'PRICE'));

    // Write to log without waiting
    register_shutdown_function(function() use ($basketValue) {
        BasketAnalyticsLog::record([
            'fuser_id'    => \Bitrix\Sale\Fuser::getId(),
            'basket_value' => $basketValue,
            'items_count'  => count($arResult['ITEMS']),
            'timestamp'   => time(),
        ]);
    });
}

component_epilog and Ajax components

The standard sale.order.ajax component uses AJAX to update checkout steps. In this case component_epilog.php executes on every AJAX request, which can lead to duplicate JS on the page.

Check that the request is not an AJAX request:

// component_epilog.php
if (\Bitrix\Main\Context::getCurrent()->getRequest()->isAjaxRequest()) {
    return; // do not output JS on AJAX requests
}

// Main epilog code here

Working with $arResult in epilog

In component_epilog.php, $arResult is the same array that was in result_modifier.php and template.php. It is available for reading, but changes to it do not affect the already-rendered HTML. Changes to $arResult in epilog only affect code that you execute yourself inside epilog.

// component_epilog.php
// This will NOT affect the already-rendered template.php:
$arResult['NAME'] = 'New name'; // pointless

// But you can use $arResult to build JS or API requests:
$analyticsData = [
    'product_id' => $arResult['ID'],
    'in_stock'   => ($arResult['CATALOG_QUANTITY'] ?? 0) > 0,
];

Template folder structure with both files

/local/templates/main/components/bitrix/catalog.element/default/
    template.php          — HTML template
    result_modifier.php   — modifies $arResult before render
    component_epilog.php  — JS, analytics, side effects after render
    .description.php      — template metadata (optional)
    style.css             — styles (optional)
    script.js             — scripts (optional)

Both files are complementary tools: result_modifier.php for data, component_epilog.php for actions. Together they enable complete customization of a standard component's behavior without touching its source code.

Scope of work: writing component_epilog.php for a specific component with analytics and side effects — 4–8 hours. With a view counter queue and agent — another 1–2 days.