Setting up Google Tag Manager on 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
    1189
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    813
  • 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
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Google Tag Manager Setup on 1C-Bitrix

GTM is a layer between your site and analytics. Instead of asking developers to add pixels and tracking codes each time, marketers configure triggers and tags in GTM themselves. But for this to work, developers need to correctly install GTM once and set up dataLayer with e-commerce events.

Installing GTM into Bitrix Template

GTM requires two code snippets: one in <head>, another right after <body>. In Bitrix template (/bitrix/templates/[name]/header.php):

<?php
// GTM container ID from Google Tag Manager account
$gtmId = \Bitrix\Main\Config\Option::get('custom', 'gtm_container_id', 'GTM-XXXXXXX');
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <!-- GTM in head (as early as possible) -->
    <?php if ($gtmId): ?>
    <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
    })(window,document,'script','dataLayer','<?= htmlspecialchars($gtmId) ?>');</script>
    <?php endif; ?>
</head>
<body>
    <!-- GTM noscript right after <body> -->
    <?php if ($gtmId): ?>
    <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=<?= htmlspecialchars($gtmId) ?>"
    height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
    <?php endif; ?>

Store container ID in b_option (settings table) via admin interface — not in code, so marketers can change it without deployment.

dataLayer for Product Pages

For extended e-commerce (GA4 Ecommerce) you need dataLayer with product data. In bitrix:catalog.element component template or in result_modifier.php:

// bitrix/templates/.default/components/bitrix/catalog.element/.default/result_modifier.php
if (!empty($arResult['CATALOG_PRICE']['BASE']['PRICE'])) {
    $product = [
        'item_id' => $arResult['ID'],
        'item_name' => $arResult['NAME'],
        'price' => (float)$arResult['CATALOG_PRICE']['BASE']['PRICE'],
        'currency' => $arResult['CATALOG_PRICE']['BASE']['CURRENCY'],
        'item_category' => $arResult['SECTION']['NAME'] ?? '',
        'item_brand' => $arResult['PROPERTIES']['BRAND']['VALUE'] ?? ''
    ];

    $this->arResult['GTM_PRODUCT'] = $product;
}

In component template output dataLayer before closing </body>:

<?php if (!empty($arResult['GTM_PRODUCT'])): ?>
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    event: 'view_item',
    ecommerce: {
        currency: '<?= htmlspecialchars($arResult['GTM_PRODUCT']['currency']) ?>',
        value: <?= (float)$arResult['GTM_PRODUCT']['price'] ?>,
        items: [<?= json_encode($arResult['GTM_PRODUCT'], JSON_UNESCAPED_UNICODE) ?>]
    }
});
</script>
<?php endif; ?>

Purchase Event After Payment

The most important event — actual purchase. In bitrix:sale.order.ajax component or on "Thank You" page:

<?php
// Get user's last order
$orderId = (int)($_SESSION['SALE_ORDER_ID_REDIRECTED'] ?? 0);
if ($orderId > 0) {
    $order = \Bitrix\Sale\Order::load($orderId);
    if ($order) {
        $items = [];
        foreach ($order->getBasket() as $basketItem) {
            $items[] = [
                'item_id' => $basketItem->getProductId(),
                'item_name' => $basketItem->getField('NAME'),
                'price' => (float)$basketItem->getPrice(),
                'quantity' => (int)$basketItem->getQuantity()
            ];
        }
?>
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
    event: 'purchase',
    ecommerce: {
        transaction_id: '<?= $order->getId() ?>',
        value: <?= (float)$order->getPrice() ?>,
        currency: '<?= $order->getCurrency() ?>',
        items: <?= json_encode($items, JSON_UNESCAPED_UNICODE) ?>
    }
});
</script>
<?php
    }
}
?>

Add to Cart via JS

The add_to_cart event is generated in JavaScript when clicking "Add to Cart". Intercept Bitrix standard event:

// In template or in /local/templates/.default/js/analytics.js
BX.addCustomEvent('OnBasketChange', function(event) {
    if (event.action === 'ADD') {
        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push({
            event: 'add_to_cart',
            ecommerce: {
                currency: event.currency || 'RUB',
                value: event.price * event.quantity,
                items: [{
                    item_id: String(event.productId),
                    item_name: event.productName || '',
                    price: event.price,
                    quantity: event.quantity
                }]
            }
        });
    }
});

The OnBasketChange event is generated by sale module via BX.Event when AJAX-adding to cart.

Verifying It Works

Install Google's "Tag Assistant" extension in browser. It shows which GTM tags fired, with what data, and any dataLayer errors.

Or from browser console:

// View all events in dataLayer
console.table(window.dataLayer);

// Watch for new events
const origPush = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = function(data) {
    console.log('dataLayer push:', data);
    return origPush(data);
};

Storing Container ID

Don't hardcode GTM ID in template — use b_option:

// Write via API
\Bitrix\Main\Config\Option::set('custom', 'gtm_container_id', 'GTM-XXXXXXX');

// Read
$gtmId = \Bitrix\Main\Config\Option::get('custom', 'gtm_container_id');

This allows changing ID without code edits via admin interface or custom module settings.