Главная мысль этой статьи простая и приземленная: dataLayer — это просто глобальная переменная в window. Всё остальное — это работа разработчика по задачам аналитика и последующий тюнинг аналитиком в тег-менеджере.
Никакой привязки к конкретному инструменту. Это не «функция GTM». Это не «фича аналитиков». Это часть фронтенда. Я пишу это как аналитик, который слишком много раз слышал что-то подобное: «Мы не можем, это же GTM».
Можем. Потому что это — JavaScript. И только потом — аналитика.
Глобальная переменная в браузере
Начнем с базы. В браузере есть глобальный объект window. Всё, что объявлено на верхнем уровне, по сути, становится его свойством.
Когда мы пишем:
<script>
window.dataLayer = window.dataLayer || [];
</script>
мы не делаем ничего сложного. Мы просто говорим:
- если
dataLayerуже существует — используй его; - если нет — создай новый пустой массив.
Это обычная инициализация. dataLayer — это массив, он живет внутри window. Он доступен любому скрипту на странице, фреймворку, самописному JS, стороннему виджету или тег-менеджеру.
Глобальная область видимости означает, что переменная существует в рамках всей страницы, а не внутри функции или модуля. Пока страница открыта, она доступна. Почему это важно объявлять до загрузки тег-менеджера? Потому что менеджер при инициализации «подписывается» на этот массив. Если переменная появится позже или будет перезаписана, он просто не увидит часть данных.
Еще раз важный архитектурный момент: тег-менеджер не создает dataLayer как концепцию. Он лишь использует уже существующую глобальную переменную как точку входа. По сути, это контракт между фронтом и аналитикой. Фронт кладет данные в массив. Инструмент их читает. Остальное — детали реализации.
Почему это массив, а не объект
Логичный вопрос разработчика: почему вообще массив? Почему не обычный объект вида window.dataLayer = {}? Ответ уходит корнями в историю появления Google Tag Manager. Изначально dataLayer задумывался как очередь команд, примерно так же, как раньше работали асинхронные трекеры аналитики. Скрипт еще не загрузился, а данные уже можно «складывать в очередь». Когда инструмент инициализируется, он проходит по накопленным элементам и обрабатывает их. Массив идеально подходит для такой модели: он хранит последовательность действий, сохраняет порядок событий, позволяет добавлять новые элементы через .push(). Объект бы постоянно перезаписывался. Массив же накапливает историю.
Когда вы пишете:
dataLayer.push({
event: 'add_to_cart',
ecommerce: {
value: 1990,
currency: 'RUB'
}
});
происходит очень простая вещь. .push() добавляет объект в конец массива. Тег-менеджер, который уже «подписался» на этот массив, перехватывает этот вызов. Каждый пуш для него — это сигнал «появилось новое событие, проверь триггеры».
Это и есть событийная модель. Мы не меняем состояние глобального объекта. Мы генерируем последовательность событий. Каждое событие — атомарное действие, которое можно обработать независимо.
С точки зрения архитектуры это намного чище:
- нет гонок из-за перезаписи значений;
- сохраняется порядок событий;
- можно обрабатывать данные, даже если они были добавлены до загрузки менеджера.
По сути, dataLayer — это примитивная event queue на глобальном уровне страницы.
Как тег-менеджеры используют dataLayer
Все популярные тег-менеджеры используют одну и ту же модель. Отличается интерфейс, но не принцип.
dataLayer в Google Tag Manager
В Google Tag Manager при загрузке происходит интересная вещь: он «перехватывает» массив. Если упростить, GTM подменяет метод .push() своей оберткой. Снаружи это всё тот же массив, а внутри уже логика обработки. Типичный код подключения выглядит так:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
</script>
Что здесь происходит? event — это триггер. Все остальные параметры — это данные, которые станут переменными внутри GTM.
Когда выполняется пуш, GTM:
- Получает объект.
- Проверяет, есть ли триггер на событие (
event). - Если условие выполнено — запускает связанные теги.
Как уже писал выше, GTM не создает dataLayer как концепцию, он просто использует уже объявленный глобальный массив как входную точку для данных.
Data Layer в Яндекс Тег Менеджере
Здесь используется та же модель. По умолчанию имя переменной — dataLayer, но его можно изменить в настройках контейнера. Это по-прежнему глобальная переменная в window. Событийная логика аналогична GTM: есть событие (event), есть данные, есть триггеры.
Пример:
dataLayer.push({
event: 'form_submit',
form_id: 'lead_form'
});
Дальше менеджер:
- Слушает вызовы
.push(). - Проверяет условия триггеров.
- Передает значения в теги.
Отличия касаются интерфейса: как создаются переменные, как настраиваются триггеры, как устроена отладка. Фундамент остается прежним: глобальный массив + пуш + обработчик событий. Менеджеры разные — модель одна.
Data Layer в Matomo Tag Manager
В Matomo Tag Manager переменная называется иначе — window._mtm. Но технически это то же самое.
window._mtm = window._mtm || [];
window._mtm.push({
event: 'purchase',
revenue: 2990
});
Это массив, и он лежит в window. Он так же работает через .push(). Когда загружается Matomo Tag Manager, он так же перехватывает метод .push() и начинает обрабатывать каждое добавленное событие. Название отличается, архитектура — нет.
Жизненный цикл dataLayer
Создание
Инициализация должна происходить раньше любого подключения контейнера. Причина простая: менеджер при старте читает текущее состояние массива и затем «подписывается» на его изменения. Если переменная появится позже, часть данных будет потеряна.
Типичный корректный порядок:
- Инициализация
window.dataLayer. - Возможные ранние пуши (например, конфигурационные события).
- Подключение контейнера.
- Дальнейшие события приложения.
Почему порядок критичен? Потому что менеджер не волшебник. Он не восстанавливает события задним числом, если вы создали массив после его загрузки. Он работает с тем, что есть на момент инициализации, и слушает дальнейшие изменения.
Типичная ошибка выглядит так: сначала подключают тег-менеджер, потом где-то ниже объявляют window.dataLayer = [];.
В этот момент происходит перезапись переменной. Если менеджер уже успел обернуть .push(), вы уничтожаете его обертку и создаете новый «чистый» массив. Визуально всё работает. Фактически события больше не обрабатываются.
Вторая распространенная проблема — повторная инициализация:
window.dataLayer = [];
Без проверки существования. Это сбрасывает всю накопленную очередь. Именно поэтому базовая конструкция window.dataLayer = window.dataLayer || []; — не просто шаблон, а защита от архитектурной ошибки.
Добавление элементов
Когда вызывается:
dataLayer.push({ event: ‘add_to_cart’ });
происходит несколько шагов. Сначала в массив добавляется объект. Это обычное поведение JavaScript. Затем срабатывает перехваченная функция .push(). Менеджер подменяет стандартный метод своей оберткой. Внутри нее:
- Данные добавляются в массив.
- Менеджер получает уведомление о новом элементе.
- Запускается механизм проверки триггеров.
Дальше система проходит по списку условий. Если есть триггер на event = add_to_cart, соответствующие теги выполняются. С точки зрения браузера всё это укладывается в обычную модель event loop. push() — синхронная операция. Она выполняется в текущем call stack. После завершения вызова управление возвращается основному потоку. Если внутри менеджера есть асинхронные операции (загрузка скриптов, отправка запросов), они уже ставятся в очередь задач. Важно понимать: push() — это не «отправка данных в аналитику», это генерация события внутри страницы.
Архитектурный подход к работе с dataLayer
Если относиться к dataLayer как к «месту, куда можно что-нибудь положить», очень быстро получится хаос. События будут называться по-разному. Структуры будут отличаться от страницы к странице. Поля начнут менять формат в зависимости от настроения разработчика. В этот момент аналитика становится нестабильной системой. dataLayer — это интерфейс. А любой интерфейс требует спецификации.
Именно поэтому появляется понятие Data Layer Specification — документа, который описывает правила взаимодействия. В него обычно входит:
- перечень событий и их назначение;
- структура e-commerce блоков;
- обязательные поля для каждого события;
- форматы данных (строка, число, ISO-дата, boolean и т. д.);
- правила именования.
Это уже контракт. Фронтенд обязуется отправлять данные в определенном формате. Аналитика обязуется корректно их обрабатывать. Когда спецификация есть, подключение любого тег-менеджера — вопрос техники. Если же спецификации нет, каждый релиз превращается в ручную отладку.
Антипаттерны
Есть несколько типичных архитектурных ошибок:
- Перезапись
dataLayer = {}уничтожает очередь и ломает обработку событий. Использование объекта вместо массива убивает событийную модель. Вы теряете последовательность действий и начинаете перезаписывать состояние. - Пуш без
event— менеджеру не за что зацепиться. Без явного триггера вы создаете данные, но не событие. - Дубли e-commerce — повторная отправка одного и того же блока в разных событиях без контроля приводит к некорректной аналитике и задвоению транзакций.
- Динамическое переопределение переменной — изменение имени
dataLayerили_mtmпосле загрузки контейнера фактически отключает связь между страницей и менеджером.
Все эти ошибки происходят по одной причине: к dataLayer относятся как к «технической детали». На практике это точка интеграции. И к ней стоит относиться как к публичному API внутри фронтенд-архитектуры.
Практические кейсы
Передача данных о пользователе
Один из самых частых запросов от аналитики — «передайте данные о пользователе в dataLayer». И вот здесь начинается тонкая грань между удобством и нарушением закона.
Базовый пример выглядит так:
dataLayer.push({
event: 'user_ready',
user: {
id: '12345',
role: 'client'
}
});
Технически это обычное событие. Есть event, есть объект user с набором свойств. Менеджер получает событие, переменные становятся доступными в тегах.
Когда так можно делать?
Когда передаются обезличенные данные: внутренний ID, сегмент, роль, тип клиента, статус авторизации. То есть то, что не позволяет напрямую идентифицировать человека вне вашей системы.
Когда так делать нельзя?
Когда в dataLayer начинают попадать email, телефон, Ф. И. О. или любые другие персональные данные в явном виде. dataLayer — это клиентская сторона. Любой пользователь может открыть DevTools и посмотреть содержимое массива.
Коротко о праве. В ЕС действует GDPR, в России — №152-ФЗ «О персональных данных». Оба документа сходятся в одном: персональные данные должны обрабатываться законно, с определенной целью и с минимизацией объема. Пушить email в глобальную переменную страницы — почти всегда плохая идея. Архитектурное правило простое: dataLayer для поведенческих и служебных данных. Идентифицирующие данные, только если они хешированы и действительно нужны для конкретной интеграции.
E-commerce события
E-commerce — это место, где различия форматов становятся особенно заметны. Подробнее про e-commerce я писал в цикле статей ранее (первая статья цикла). В Google Tag Manager исторически использовалась модель Enhanced Ecommerce для Universal Analytics. Там структура выглядела примерно так:
dataLayer.push({
event: 'addToCart',
ecommerce: {
add: {
products: [{
id: 'sku_1',
name: 'Product 1',
price: 1990
}]
}
}
});
С переходом на Google Analytics 4 структура изменилась. Теперь модель событийная, а товары передаются в массиве items:
window._mtm.push({
event: 'add_to_cart',
ecommerce: {
items: [{
sku: 'sku_1',
name: 'Product 1',
price: 1990,
quantity: 1
}]
}
});
Различия в названиях полей, вложенности, обязательных параметрах. Но принцип остается тем же: событие + структурированные данные. Именно поэтому важно не «копировать пример из документации», а проектировать свою Data Layer Specification так, чтобы она могла быть адаптирована под разные инструменты. Они меняются, тогда как архитектура данных должна быть стабильной.
Итоговая модель понимания
Если отбросить бренды, интерфейсы и маркетинг, остается очень простая модель, строящаяся на простых аксиомах:
dataLayer— это глобальная переменная.- Это массив.
- Он живет в
window. - Тег-менеджер его не создает, а использует.
- Это интерфейс между фронтом и аналитикой.
Как только вы начинаете воспринимать dataLayer как внутренний API страницы, половина проблем исчезает. Появляется логика версионирования, документации и ответственности.
Когда dataLayer не нужен
Есть ситуации, где он действительно избыточен. Если используется server-side трекинг и данные отправляются напрямую с сервера, без клиентской логики. Если аналитика встроена напрямую в код приложения, без тег-менеджера и без необходимости промежуточного слоя. Если в проекте уже есть полноценный custom event bus и аналитика подписывается на него напрямую, минуя глобальную переменную. Во всех остальных случаях dataLayer остается самым простым и универсальным способом связать интерфейс и аналитику.
Заключение
dataLayer — это не инструмент маркетинга, а часть фронтенд-архитектуры. Хорошо спроектированный dataLayer экономит десятки часов на отладке, упрощает масштабирование и делает подключение новых систем предсказуемым. Плохо спроектированный превращает аналитику в вечный режим «почини после релиза». Или другими словами и коротко: если фронтенд — это API для пользователя, то dataLayer — это API для аналитики.