Пользовательские шаблоны в GTM: разбираемся на примере

Недавно в Google Tag Manager появились пользовательские шаблоны. Какие возможности дает этот инструмент и как с ним работать, объясняет веб-аналитик Adventum Олег Денисов.

В конце мая компания Google представила новую фичу в Google Tag Manager (GTM): Custom Templates или пользовательские шаблоны. Давайте разберемся, зачем она нужна, как ей пользоваться, в чем отличия от HTML-тегов и JavaScript (далее — JS) переменных. В качестве примера рассмотрим создание Custom Template для пикселя динамического ретаргетинга «ВКонтакте» и дальнейшую настройку тегов GTM через него.

Простыми словами о Custom Templates

Custom Templates представляют из себя шаблоны, по которым пользователи могут создавать новые теги или переменные. В GTM есть уже готовые шаблоны (в разделе Featured или Рекомендуемые), например, тег Google Analytics, Google Optimize и прочие. Теперь мы можем дополнить их своими шаблонами. После создания они появятся во вкладке Custom:

Пример

Ключевое отличие от HTML-тегов и JS-переменных в том, что когда пользователь создает тег или переменную по готовому шаблону, он не взаимодействует с JS-кодом.

Код пользовательского шаблона пишет разработчик или веб-аналитик на этапе его создания. Затем, при создании тега или переменной по пользовательскому шаблону, всё взаимодействие осуществляется через интерфейс (который также настраивается при создании пользовательского шаблона):

Интерфейс

Соответственно, по сравнению с написанием HTML-тега или JS-переменной, настроить тег или переменную по пользовательскому шаблону становится на порядок проще, так как для этого не требуются знания и навыки работы с JavaScript.

Еще один большой плюс пользовательских шаблонов — на порядок уменьшается вероятность «положить» сайт из-за ошибки в JS-коде тега.

В нашем примере для настройки тега динамического ретаргетинга «ВКонтакте» больше не нужно обращаться к разработчикам — все можно настроить самостоятельно, даже передачу данных о товарах (при настроенной на сайте расширенной электронной торговле Google), имея только опыт работы с GTM.

Создание Custom Template

Так как мы создаем шаблон тега, нам необходимо перейти в раздел Templates и нажать кнопку New в разделе Tag Templates.

Создание, шаг 1

После этого открывается Template Editor (редактор шаблона):

Редактор шаблона

В левой части редактора находится окно настроек, в правой части — окно предпросмотра и консоль. В окне настроек четыре вкладки, которые необходимы для создания и работы с шаблоном.

Вкладка Info

На этой вкладке заполняется информация о теге: название, описание, иконка. Это информация, которую мы будем видеть при создании нового тега в списке шаблонов:

Вкладка Info

Для иконки тега есть требования: формат PNG, JPEG или GIF, разрешение минимум 64×64 пикселя и размер не более 50 КБ.

Вкладка Fields

В этом разделе создается интерфейс нашего шаблона. Интерфейс представляет из себя различные поля и формы, с которыми взаимодействует пользователь на этапе создания нового тега или переменной.

В дальнейшем информация, которую пользователь ввел при создании тега с помощью интерфейса, используется в коде шаблона.

Для добавления нового элемента нажимаем кнопку Add Field. Появляется окно выбора типа элемента:

Добавление поля

GTM позволяет выбрать следующие типы элементов интерфейса:

  • текстовое поле;
  • раскрывающееся меню;
  • флажок;
  • переключатели;
  • простая таблица, здесь можно редактировать каждую ячейку.
  • расширенная таблица, можно редактировать только строку, такой тип таблицы удобно использовать для создания словаря из пар «ключ — значение»;
  • группа элементов, позволяет сгруппировать несколько типов в группу;
  • ярлык используется для добавления текста в интерфейс, не требует от пользователя введения никаких данных.

После добавления элемента необходимо присвоить ему понятное название, которое потом будет использоваться в коде. Это своего рода название переменной — оно должно быть понятным и раскрывать суть созданного элемента интерфейса. Например, название «ID» не говорит ни о чем конкретном, но название «pixelIDs» уже показывает, что в данном элементе хранятся ID пикселей, которые ввел пользователь:

Названия пикселей

Далее необходимо перейти в настройки каждого элемента и активировать необходимые свойства.

В разных ситуациях требуются разные свойства элемента интерфейса, поэтому по умолчанию они все скрыты и нам необходимо активировать именно те, которые нужны сейчас:

Свойства элементов

У разных типов элементов доступные свойства отличаются, укажу наиболее часто использующееся, которые есть почти у всех элементов:

1. Отображаемое название. Это название, которое пользователь увидит в интерфейсе при создании тега:

Название события

2. Пример значения. Это подсказка пользователю о том, какие значения вводить в поле:

Пример значения

3. Текст справки — текст, который увидит пользователь, если наведет курсор на иконку справки элемента:

Текст справки

4. Правила проверки данных. В этом свойстве можно задать определенные правила проверки данных, введенных пользователем в поле, и при необходимости текст ошибки, который появится при попытке сохранить тег с данными, которые не прошли проверку.

Например, можно указать, что поле обязательно должно быть заполнено. Второй пример: от пользователя необходимо получить адрес электронной почты, тогда можно сделать проверку, что введенные пользователем данные должны соответствовать регулярному выражению .*@.*\..* .

Проверка данных, настройка

Чтобы указать текст ошибки, который появится, если введенные данные не соответствуют правилам проверки, необходимо активировать расширенные настройки правила:

 

Также в расширенных настройках можно указать условия, при которых данное правило активируется (поле Enable Condition).

5. Условия включения. Это условия, при которых у пользователя появится данный элемент интерфейса.

Например, чтобы не перегружать шаблон элементами интерфейса, можно сделать появление нужных элементов при установке флажка. То есть предположим, если пользователь хочет настроить передачу данных о товаре в пиксель (это возможно при настроенной на сайте расширенной электронной торговле Google), то он ставит флажок «Использовать dataLayer для передачи данных о товаре», и после установки флажка в интерфейсе тега появляются элементы, необходимые для настройки такой передачи. Если флажок не стоит, то элементов в интерфейсе нет.

Условия включения

Замечу, что тут необходимо указывать название элемента, которое необходимо было присвоить ему сразу после добавления.

По мере добавления настроек и создания интерфейса все изменения можно сразу смотреть и тестировать в окне предпросмотра:

Предпросмотр

Вкладка Code

Эта вкладка представляет из себя редактор кода.

Код GTM Custom Templates пишется на «урезанном» JavaScript ES6 и выполняется в изолированной среде, где всё общение с глобальными данными (то есть непосредственно со страницей) происходит через API. Глобальных объектов, таких как window или document, в ней нет, соответственно, привычных методов тоже. Например, конструкторы (new Object и подобные), setTimeout, parseInt, delete и т.д. — всё это не будет работать в Custom Template.

Но для всего этого есть API. И, следовательно, написание кода для Custom Template должно начинаться с того, что мы определяем API, которые будем использовать в нашем коде:

//Используемые API

//метод получения значения глобальной переменной
const copyFromWindow = require('copyFromWindow');

//метод установки значения глобальной переменной
const setInWindow = require('setInWindow');

//метод установки стороннего скрипта на страницу
const injectScript = require('injectScript');

//метод вызова глобальной функции
const callInWindow = require('callInWindow');

//метод преобразования данных, полученных из расширенной таблицы интерфейса, в стандартный объект
const makeTableMap = require('makeTableMap');

//метод работы с URL страницы
const getUrl = require('getUrl');

//метод получения параметров запроса
const getQueryParameters = require('getQueryParameters');

//метод преобразования в целое число
const makeInteger = require('makeInteger');

//метод преобразования в строку
const makeString = require('makeString');

//метод аналог setTimeout
const callLater = require('callLater');

//метод аналог console.log
const logToConsole = require('logToConsole');

Полный список API с подробной документацией находится в справке Google для разработчиков.

 

Покажу на примерах, как работать с API:

Описание действия

Классический JS

Custom Template API

Вывод в консоль

console.log(‘Hi’);

logToConsole(‘Hi’);

Установить таймер

setTimeout(function,100);

callLater(function);

Преобразование в строку

String(1234);

makeString(1234);

Преобразование в целое число

parseInt(‘1234’,10);

makeInteger(‘1234’);

Хост страницы

window.location.hostname

getUrl(’host’);

Как видно из таблицы примеров, после определения API его необходимо использовать вместо стандартных JS-конструкций.

После того как мы определили API, желательно определить объект с настройками, которые ввел пользователь. Это можно сделать и после, например, во время исполняемого кода, запрашивая необходимые данные из настроек пользователя. Но если мы определяем объект настроек с самого начала, далее работать с кодом становится проще и понятнее, так как все настройки пользователя хранятся в отдельном объекте.

Чтобы получить данные из элемента интерфейса, необходимо воспользоваться конструкцией data.{{название элемента}}:

//объект с настройками пользователя
const settings = {
   //название события
   event: data.event,

   //ID пикселя (пикселей)
   pixelIDs: data.pixelIDs,

   //ID прайс-листа (если используем 1 прайс-лист)
   priceListId: data.priceListId,

   //используем ли несколько прайс-листов?
   fewPriceLists: data.fewPriceLists,

   //ID прайс-листов (если используем несколько прайс листов)
   priceListIds: data.priceListIds === undefined ? data.priceListIds : makeTableMap(data.priceListIds,'hostname','priceListId'),

   //используем ли ecommerce для передачи товаров?
   ecommerceUse: data.ecommerceUse,

   //объект ecommerce для события
   eventEcommerce: data.eventEcommerce,

   //параметр поискового запроса по сайту
   siteSearchQueryParam: data.siteSearchQueryParam
};

Примечание: если в метод makeTableMap передать undefined, это вызовет ошибку скрипта, поэтому я использую конструкцию с тернарным оператором (сокращенная запись конструкции if-else), чтобы отфильтровать такие сценарии.

О методе makeTableMap. Если в интерфейсе используется расширенная таблица, то данные в ней хранятся в таком виде:

[
   {'key': 'k1', 'value': 'v1'},
   {'key': 'k2', 'value': 'v2'}
]

После обработки методом makeTableMap данные становятся обычным объектом с парами «ключ — значение»:

{
   'k1': 'v1',
   'k2': 'v2'
}

Еще одно требование к коду Custom Template: в случае успешного выполнения тега необходимо вызывать метод data.gtmOnSuccess(), а в случае ошибки — метод data.gtmOnFailure().

Например, в моем коде метод data.gtmOnSuccess() вызывается после успешной отправки запроса, а метод data.gtmOnFailure() — в случае неудачной загрузки на странице внешнего скрипта VK openapi.js.

После определения API и определения объекта с настройками можно приступать к написанию алгоритма отработки пикселя.

Тут главное помнить следующее:

  • Если необходимо получить глобальную переменную — используем метод API copyFromWindow.

    copyFromWindow('VK');
    //VK - имя глобальной переменной, значение которой необходимо получить
    
  • Если необходимо задать глобальную переменную — используем метод API setInWindow.

    setInWindow('openapiInject', 1);
    //openapiInject - имя глобальной переменной, значение которой устанавливаем
    //1 - значение, которое присваиваем глобальной переменной.
    
  • Если необходимо запустить глобальную функцию — используем метод API callInWindow.

    callInWindow('VK.Retargeting.Init', p);
    //VK.Retargeting.Init - имя глобальной функции, которую необходимо вызвать
    //p - параметр, который передаем в запускаемую функцию
    
  • Если необходимо добавить внешний скрипт на страницу — используем метод API injectScript.

    injectScript('https://vk.com/js/api/openapi.js?159', pixel.setVkAsyncInit(), data.gtmOnFailure, 'vkPixel');
    //https://vk.com/js/api/openapi.js?159 - адрес скрипта, который необходимо внедрить на страницу
    //pixel.setVkAsyncInit() - функция, которая запускается в случае успешной загрузки скрипта
    //data.gtmOnFailure - функция, которая запускается в случае ошибки загрузки скрипта
    //vkPixel - необязательная строка, которая указывает, что предоставленный URL необходимо кешировать. Если задать это значение, для запроса JavaScript будет создан только один элемент скрипта
    
  • Если необходимо получить URL (или его часть) — используем метод API getUrl.

    getUrl('host');
    //host - параметр URL, который запрашиваем. Доступные параметры, кроме host: protocol, port, path, extension, fragment, query.
    

Как я уже писал выше, Custom Template поддерживает JS ES6. Желательно пользоваться этим синтаксисом, так как он сокращает код и делает его более читабельным, а работу JS — более предсказуемой и похожей на другие языки программирования.

Основное, что желательно использовать, — это стрелочные функции и объявления переменных const и let вместо var.

Переменная, объявленная через const, — это константа, значение которой нельзя изменить.

Переменная, объявленная через let, отличается от объявленной через var следующим:

  • let не добавляется к глобальному объекту window;
  • видимость переменной let ограничивается блоком объявления;
  • переменные, объявленные через let, нельзя объявить повторно.

Стрелочные функции представляют из себя сокращенное написание обычных функций:

//1. Стандартная функция
const func1 = function() {
   return 'test';
}
//Аналогичная ей стрелочная функция
const func1 = () => 'test';

//2. Стандартная функция
const func2 = function(arg) {
   if (arg > 0) return 'plus';
   else return 'minus';
}
//Аналогичная ей стрелочная функция
const func2 = arg => {
   if (arg > 0) return 'plus';
   else return 'minus';
}

//3. Стандартная функция
const func3 = function(arg1, arg2){
   if (arg1 > arg2) return arg1;
   else return arg2;
}
//Аналогичная ей стрелочная функция
const func3 = (arg1, arg2) => {
   if (arg1 > arg2) return arg1;
   else return arg2;
}

Теперь понимая, как пользоваться Custom Template API, можем написать код работы тега, используя синтаксис JavaScript ES6.

Мой код содержит в себе методы запуска пикселя, установки VK openapi.js, получения данных о товаре из dataLayer (при настроенной на сайте расширенной электронной торговле Google), обработки этих данных для приведения их в вид, необходимый для отправки в пиксель ретаргетинга «ВКонтакте», и метод отправки события.

Метод запуска пикселя поддерживает три сценария работы:

  1. Пиксель запускается на странице, где отсутствует openapi.js.
  2. Пиксель запускается на странице, где есть openapi.js, но она еще не подгрузилась.
  3. Пиксель запускается на странице с загруженной openapi.js.

Полный код пикселя:

//api
const copyFromWindow = require('copyFromWindow');
const setInWindow = require('setInWindow');
const injectScript = require('injectScript');
const callInWindow = require('callInWindow');
const makeTableMap = require('makeTableMap');
const getUrl = require('getUrl');
const getQueryParameters = require('getQueryParameters');
const makeInteger = require('makeInteger');
const makeString = require('makeString');
const callLater = require('callLater');

//объект с настройками пользователя
const settings = {
   event: data.event,
   pixelIDs: data.pixelIDs,
   priceListId: data.priceListId,
   fewPriceLists: data.fewPriceLists,
   priceListIds: data.priceListIds === undefined ? data.priceListIds : makeTableMap(data.priceListIds,'hostname','priceListId'),
   ecommerceUse: data.ecommerceUse,
   eventEcommerce: data.eventEcommerce,
   siteSearchQueryParam: data.siteSearchQueryParam
};

//основной объект с методами и свойствами пикселя
const pixel = {
   //метод определения хоста страницы
   getPageHostname: () => getUrl('host'),

   //метод получения глобального объекта VK
   getVK: () => copyFromWindow('VK'),

   //метод установки глобального коллбэка VK
   setVkAsyncInit: () => {
       setInWindow('vkAsyncInit', pixel.sendEvent);
   },

   //метод определения поискового запроса пользователя на сайте
   getSiteSearchPhrase: () => {
       if (settings.event === 'view_search') return getQueryParameters(settings.siteSearchQueryParam);
       else return undefined;
   },

   //метод определения параметров события для пикселя
   getEventParams: (products, currencyCode, revenue) => {
       let eventParamsClean= {};
       let eventParams = {
           products: eventProducts.getProductParams(products),
           category_ids: eventProducts.getCategoryString(products),
           currency_code: currencyCode,
           total_price: eventProducts.getTotalPrice(products, revenue),
           search_string: pixel.getSiteSearchPhrase()
       };
       if (eventParams.products !== undefined) eventParamsClean.products = eventParams.products;
       if (eventParams.category_ids !== undefined) eventParamsClean.category_ids = eventParams.category_ids;
       if (eventParams.currency_code !== undefined) eventParamsClean.currency_code = eventParams.currency_code;
       if (eventParams.total_price !== undefined) eventParamsClean.total_price = eventParams.total_price;
       if (eventParams.search_string !== undefined) eventParamsClean.search_string = eventParams.search_string;
       return eventParamsClean;
   },

   //метод выбора прайс-листа
   getPriceListId: hostname => {
       if (settings.fewPriceLists) return settings.priceListIds[hostname];
       else return settings.priceListId;
   },

   //метод инициализации openapi.js
   openapiInit: () => {
       injectScript('https://vk.com/js/api/openapi.js?159', pixel.setVkAsyncInit(), data.gtmOnFailure, 'vkPixel');
       setInWindow('openapiInject', 1);
   },

   //метод отправки события
   sendEvent: () => {
       if (settings.event === 'hit') {
           settings.pixelIDs.split(',').forEach(p => {
               callInWindow('VK.Retargeting.Init',p);
               callInWindow('VK.Retargeting.Hit');
           });
       } else {
           const pricelist = pixel.getPriceListId(pixel.getPageHostname());
           const name = settings.event;
           let products = [];
           if(settings.ecommerceUse) products = name === 'view_home' || name === 'view_category' || name === 'view_search' || name === 'view_other' ? settings.eventEcommerce : settings.eventEcommerce.products;
           else products = undefined;
           const currencyCode = settings.ecommerceUse ? settings.eventEcommerce.currencyCode : undefined;
           const revenue = (settings.ecommerceUse && name === 'purchase') ? settings.eventEcommerce.actionField.revenue : undefined;
           const eventParams = settings.ecommerceUse ? pixel.getEventParams(products, currencyCode, revenue) : undefined;
           settings.pixelIDs.split(',').forEach(p => {
               callInWindow('VK.Retargeting.Init',p);
               callInWindow('VK.Retargeting.ProductEvent', pricelist, name, eventParams);
           });
       
   },

   //метод запуска пикселя
   start: () => {
       if (pixel.getVK() === undefined && copyFromWindow('openapiInject') !== 1) {
           pixel.openapiInit();
           data.gtmOnSuccess();
       } else if (pixel.getVK() === undefined && copyFromWindow('openapiInject') === 1) {
           if (pixel.count < 50) {
               callLater(pixel.start);
               pixel.count++;
           } else return;
       } else {
           pixel.sendEvent();
           data.gtmOnSuccess();
       
   },

   //счетчик попыток запуска
   count: 0
};

//объект с методами обработки массива продуктов события
const eventProducts = {
   //метод определения параметров объекта products для пикселя
   getProductParams: products => {
       let arr = [];
       products.forEach(i => {
           let productParamsClean = {};
           let productParams = {
               id: makeString(i.id),
               group_id: makeString(i.brand),
               price: makeInteger(i.price * 100) / 100
           };
           if (productParams.id !== 'undefined') productParamsClean.id = productParams.id;
           if (productParams.group_id !== 'undefined') productParamsClean.group_id = productParams.group_id;
           if (productParams.price !== 0) productParamsClean.price = productParams.price;
           arr.push(productParamsClean);
       });
       return arr;
   },

   //метод собирает категории товаров в строку вида 'a,b,c' с проверкой на уникальность каждой категории
   getCategoryString: products => {
       let categoryId = '';
       let check = [];
       products.forEach(i => {
           if(check.indexOf(i.category) === -1) {
               check.push(i.category);
               categoryId += ',' + i.category;
           
       });
       return categoryId.slice(1);
   },

   //метод определения общей стоимости товаров
   getTotalPrice: (products, revenue) => {
       let sumPrice = 0;
       if (revenue !== undefined ) return makeInteger(revenue * 100) / 100;
       else {
           products.forEach(i => {
               if (i.hasOwnProperty('quantity')) sumPrice += (makeInteger(i.price * 100) / 100) * makeInteger(i.quantity);
               else sumPrice += makeInteger(i.price * 100) / 100;
           });
           return sumPrice;
       
   
};

//запускаем пиксель
pixel.start();

Вкладка Permissions

После написания кода тега остается финальный этап — выдать разрешения на взаимодействия с глобальными данными страницы. Это делается как раз на вкладке Permissions.

Так как код выполняется в изолированной среде, а взаимодействие с глобальными данными происходит через API, то для каждого метода API (где это необходимо) нам нужно вручную указать разрешения на те или иные действия.

Делается это для того, чтобы максимально обдуманно подходить к работе с глобальными данными страницы, и, тем самым, максимально снизить шансы «положить» сайт ошибкой в коде нашего шаблона.

Для используемых в моем коде методов API необходимо выдать три типа разрешений:

Разрешения

1. Accesses Global Variables — доступ на чтение, запись, выполнение к глобальным переменным, которые используются в нашем коде. Переменные необходимо добавлять вручную и для каждой из них указать, что мы разрешаем делать.

Доступы

Например, переменную VK можно только читать, vkAsyncInit можно читать и переопределять, а метод VK.Retargeting.Hit можно только выполнять.

2. Reads URL. Тут необходимо указать, какие части URL разрешено получать. Я разрешаю получать любые части URL:

Чтение URL

Но при желании можно указать какие-то конкретные:

Конкретные чтения

3. Injects Scripts. Тут необходимо прописать адреса, с которых можно загружать внешние скрипты. В моем коде загружается только один скрипт с VK openapi.js, его адрес я и указываю:

Скрипты

На этом всё, настройка Custom Template завершена, можно сохранять шаблон и переходить к тестированию.

Настройка и тестирование тега по созданному Custom Template

В качестве примера создадим два тега динамического ретаргетинга «ВКонтакте» с помощью созданного Custom Template: pageview и addToCart.

Pageview

Зайдем в нужный контейнер GTM, создаем новый тег, тип тега выбираем VK Pixel в разделе Custom:

Настройка, шаг 1

Заполняем название тега, отслеживаемое событие выбираем Hit (это стандартный pageview), в поле «ID пикселей» через запятую указываем ID двух пикселей, в которые будут отправляться данные, и ставим триггер All Pages:

Название

Сохраняем созданный тег.

AddToCart

Создание тега для события добавления товара в корзину будет немного посложнее, чем тега Hit.

Во-первых, необходимо передавать товар, который добавляется в корзину. Как я уже писал выше, это возможно при настроенной на сайте расширенной электронной торговле Google. В таком случае данные о товаре берутся из dataLayer.

Для этого нам надо создать в GTM переменную dataLayer, которая будет хранить в себе объект ecommerce для события addToCart. Настройки переменной выглядят вот так:

Настройки переменной

Во-вторых, нужно создать триггер, который будет активировать тег при наступлении события ecommerce addToCart (триггер будет активировать тег при пуше в dataLayer при событии addToCart):

Создание триггера

После создания переменной с объектом ecommerce и триггера можно приступать к созданию тега:

Создание тега

По порядку:

  1. В качестве отслеживаемого события выбираем Add To Cart.
  2. Заполняем через запятую ID двух пикселей, в которые необходимо передавать данные.
  3. Устанавливаем флажок «Использовать несколько прайс-листов»: для Москвы и Петербурга в нашем примере необходимо использовать разные прайс-листы.
  4. Заполняем таблицу с прайс-листами.
  5. Устанавливливаем флажок «Использовать ecommerce для передачи товаров и параметров».
  6. В объекте ecommerce этого события указываем созданную ранее переменную.
  7. Устанавливаем триггер на отслеживаемое событие, в этом случае — AddToCart.
  8. Сохраняем.

Проверка отработки

Для проверки отработки пикселей динамического ретаргетинга «ВКонтакте» нужно активировать режим Preview в GTM, перейти на наш сайт и открыть раздел Network в консоли браузера и в поле Filter ввести ‘rtrg’:

Проверка в консоли

После этого обновляем страницу, и у нас должно появиться два запроса — событие Hit, отправленное в два пикселя:

Событие Hit

Status 200 означает, что запросы отправлены и получены сервером успешно.

Также в окне Preview GTM видим, что наш созданный тег корректно сработал на событие Page View.

Для проверки события Add To Cart добавляем товар в корзину, и в консоли у нас появляется еще два запроса:

Проверка события AddtoCart

В окне Preview GTM видим, что второй тег отработал успешно. Данные о товаре из dataLayer подтянулись и обработались корректно, также подставился корректный прайс-лист.

Для второго хоста прайс-лист также подставляется корректно:

Хост

Теги для других событий настраиваются и проверяются аналогично.

Заключение

Пользовательские шаблоны меняют привычную парадигму использования GTM. Все привыкли к HTML-тегам и JS-переменным, теперь же есть отличная альтернатива.

Достаточно один раз создать качественный пользовательский шаблон, и дальше им сможет пользоваться любой желающий, который знаком с GTM.

Вы можете скачать пользовательский шаблон пикселя динамического ретаргетинга «ВКонтакте», который мы рассматривали в этой статье. Для импорта шаблона необходимо создать новый Custom Template и в меню выбрать Import:

Импорт шаблона

Учитывая возможность делиться созданными шаблонами, думаю, они должны обрести популярность среди пользователей. 

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: