Este no es el primer post que escribo sobre Google Tag Manager (GTM) server-side. Cuanto más experimento y trabajo con esta herramienta, más potencial le veo. Cada vez tengo más claro que GTM server-side es mucho más que un tag manager: tu servidor de tagueo puede ser -y es- una pieza más de un ecosistema de API’s interconectadas.
En este post te quiero hablar de uno de estos casos de uso: cómo usar la API de BigQuery en GTM server-side para generar una base de datos en BigQuery sin pasar por Google Analytics. En concreto una base de datos de productos vendidos a través de tu ‘ecommerce’ (si tienes una tienda online, esto te interesa), pero puedes customizar la siguiente solución para generar cualquier tipo de base de datos.
Lo que te voy a contar no es nada nuevo, y como siempre, me gusta citar y enlazar a las lecturas que me han inspirado:
- ‘Creating your own cookieless analytics tool with GTM Server Side, Cloud Run, BigQuery and Shiny’, de Mark Edmonson
- ‘Write to Google BigQuery from a GTM server container’. de Simo Ahava
- ‘Google Analytics alternatives – an overview’, de Krisjan Oldekamp
La idea que subyace en todas estas lecturas es la que ya te he adelantado un poco más arriba: vas a generar una base de datos en BigQuery desde GTM server-side sin que tus hits pasen por Google Analytics. Ojo, esto es mucho. En esencia estás generando tu propia herramienta de medición.
Qué es la API de BigQuery y cómo se integra en Google Tag Manager (GTM) server-side
BigQuery es el data warehouse de Google. Forma parte del ecosistema de Google Cloud Platform y te permite almacenar, gestionar y explotar una cantidad ingente de datos a buen precio y con muy buen rendimiento.
La API de BigQuery te permite interactuar con este warehouse para realizar varias operaciones. En el contexto de Google Tag Manager server-side, la API de BigQuery posibilita enviar y registrar datos en una tabla de BigQuery desde un contenedor de GTM server-side.
El esquema es bastante sencillo, fíjate:
Envía un request de GA4 (enhanced ecommerce) a tu contenedor de GTM server-side
El primer paso para poner esta implementación en marcha es enviar a tu contenedor de GTM server-side una petición por http cada vez que se produzca una venta en tu ‘ecommerce’. Este ‘request’ debe contener la información relativa a esta compra: productos vendidos, importe, cuantía total, etc.
Google Tag Manager server-side funciona muy bien con peticiones que siguen el modelo de datos de GA4, por lo que yo te propongo usar el dataLayer de enhanced ecommerce de GA4 (evento ‘purchase’) y una petición de GA4. Yo voy a ejecutar un dataLayer.push()
con el evento ‘purchase’ y el objeto ‘ecommerce’ correspondiente desde mi contenedor web de GTM (a través de un tag de tipo HTML Personalizado). Puedes seguir esta senda o hablar con tu equipo de Desarrollo para que se encarguen en ellos. En cuanto a la petición de GA4, también la ejecutaré desde mi contenedor web de Google Tag Manager.
Bien, este es el dataLayer.push()
que voy a ejecutar desde mi ‘Custom HTML Tag’:
<script>
(function () {
var dataLayer = window.dataLayer || [];
dataLayer.push({ ecommerce: null }); // Clear the previous ecommerce object.
dataLayer.push({
event: "purchase",
ecommerce: {
transaction_id: "00023",
affiliation: "Online Store",
value: "59.89",
tax: "4.90",
shipping: "5.99",
currency: "EUR",
coupon: "DESCUENTO_NAVIDAD",
items: [{
item_name: "Camiseta azul cielo",
item_id: "12345",
price: "15.25",
item_brand: "Mi marca",
item_category: "Ropa",
item_variant: "Azul",
quantity: 1
},
{item_name: "Calcetines de cuadros escoceses",
item_id: "67890",
price: 33.75,
item_brand: "Marca Luxury",
item_category: "Ropa",
item_variant: "Rojo grante",
quantity: 1
},
{item_name: "Gafas de sol modelo maxy",
item_id: "53421",
price: "15.25",
item_brand: "Mi marca",
item_category: "Complementos",
item_variant: "Marrón",
quantity: 3
},
{item_name: "Pulsera de cuero negro",
item_id: "89543",
price: "15.25",
item_brand: "Mi marca",
item_category: "Complementos",
item_variant: "Negro",
quantity: 2}
]
}
});
})();
</script>
Y esta es la configuración del tag de GA4 que enviará la información que expone el anterior dataLayer a mi contenedor de GTM server-side. Fíjate en las variables de tipo dataLayer con las que se informa los parámetros del evento y en el trigger del tag:
Crea un dataset y una tabla en tu proyecto de BigQuery
El siguiente paso (igual debería ser el primero) es crear una tabla en BigQuery para alojar los datos de tus productos vendidos (para ello antes tendrás que crear un dataset). Es importante que tengas en cuenta tres cosas:
- En la medida de lo posible te aconsejo que crees el dataset y la tabla de BigQuery dentro del mismo proyecto de Google Cloud Platform en el que has implementado GTM server-side. Así no tendrás que preocuparte por autentificar API’s, ya que todos los recursos estarán ‘colgando’ bajo el mismo proyecto.
- Ten en cuenta dónde quieres que tu dataset y tus tablas se alojen. Yo vivo en la UE, así que las alojo en la UE.
- Piensa bien la estructura que quieres que tenga tu tabla y el formato de los datos. No es lo mismo alojar un precio en formato FLOAT que en formato STRING. Esto influirá de forma directa a la forma en que puedas explotar estos datos.
Bien, esta es la tabla que he creado yo en BigQuery (pertenece al mismo proyecto de Google Cloud en el que está implementado mi contenedor de GTM server-side). Si te fijas, los datos que voy a guardar en ella están muy ligados a la estructura del dataLayer de enhanced ecommerce de GA4. Añado, además, el campo ‘country’ y ‘city’. Estos dos últimos los genero a raíz de los ‘request headers’ de la petición entrante a mi servidor de tagueo (líneas 39, 40 y 41 del código del tag que interactúa con la API de BigQuery que te detallo más abajo) .
Fíjate en el campo item, verás que es de tipo RECORD y modo REPEATED. ¿Qué quiere decir esto? RECORD es la analogía en BigQuery (y por lo tanto en SQL) a un objeto en JavaScript. Un objeto JS tiene propiedades que tienen valores. A un RECORD le sucede lo mismo, tiene varias propiedades (item_name, item_id, price, category, etc), cada una con un valor ¿Y qué quiere decir que sea un campo REPEATED? Pues que es un campo que contiene varios RECORDS. Siguiendo la misma analogía de antes, en JavaScript sería un array que contiene varios objetos.
Fíjate en otro detalle: en el tipo de dato del campo item.price. Es un campo de tipo FLOAT, ¿verdad? Tiene sentido, los precios suelen tener decimales. Bien, haz scroll ahora al código del dataLayer.push()
que hay un poco más arriba. Todos los precios a excepción de uno son datos de tipo STRING, es decir, con campos de texto. A BigQuery no le importa el formato del dato en JavaScript. Cogerá este dato y lo grabará en la tabla como un un FLOAT.
La configuración en Google Tag Manager (GTM) server-side: la API de BigQuery
El trabajo que tienes que hacer en GTM server-side es relativamente sencillo. Lo primero es configurar un cliente que intercepte la petición entrante a tu ‘tagging server’ con la información de la venta de tu ‘ecommerce’ (evento ‘purchase’). Estás trabajando con un ‘request’ de GA4, por lo que puedes usar un cliente estándar de GA4 para interceptar esta llamada y generar un objeto.
Ahora tienes que crear un tag que te permita interactuar con la API de BigQuery desde tu contenedor de Google Tag Manager server-side. Si quieres ahorrarte trabajo, te recomiendo que busques en la ‘tag template gallery’ de GTM, encontrarás muchas soluciones ya desarrolladas. En concreto te aconsejo una: ‘Write to BigQuery’, de trakken.
Pero muchas veces a mi me gusta desarrollar mis propias soluciones, y para este ejercicio he desarrollado el siguiente tag para interactuar con esta API. Fíjate, este es el código:
//API's needed for this tag template to operate
const BigQuery = require('BigQuery');
const getAllEventData = require('getAllEventData');
const getRequestHeader = require('getRequestHeader');
const getTimestampMillis = require('getTimestampMillis');
const Math = require('Math');
const makeNumber = require('makeNumber');
const currentMoment = Math.round(getTimestampMillis()/1000);
//Tag code
const eventObject = getAllEventData();
//Code needed to generate a dynamic items array that is going to be sent to BigQuery
let itemsBigQueryArray = [];
for (let i = 0; i < eventObject.items.length; i++){
itemsBigQueryArray[i] = {
item_name: eventObject.items[i].item_name,
item_id: eventObject.items[i].item_id,
price: eventObject.items[i].price,
item_brand: eventObject.items[i].item_brand,
category: eventObject.items[i].item_category,
variant: eventObject.items[i].item_variant,
quantity: eventObject.items[i].quantity
};
}
const bigQueryObject = {
date: currentMoment,
transaction_id: eventObject.transaction_id,
affiliation: eventObject.affiliation,
//Value, tax and shipping are string values, converting them to number value with makeNumber() API to send to BigQuery table as a number to operate with later
value: makeNumber(eventObject.value),
tax: makeNumber(eventObject.tax),
shipping: makeNumber(eventObject.shipping),
currency: eventObject.currency,
item: itemsBigQueryArray,
//'Country' and 'city' are generated from the incoming request headers
country: getRequestHeader('X-Appengine-Country'),
city: getRequestHeader('X-Appengine-City')
};
//Object containing the BigQuery project, data set and table id's to which data is going to be written
const bigQueryInfo = {
projectId: data.projectName,
datasetId: data.datasetName,
tableId: data.tableName,
};
//Faulty Values Object
const faultyValuesObject = {
ignoreUnknownValues: false,
skipInvalidRows: false,
};
//BigQuery API insert operation
BigQuery.insert(bigQueryInfo, [bigQueryObject], faultyValuesObject, data.gtmOnSuccess(), data.gtmOnFailure());
Y este es el aspecto que tiene el interfaz gráfico del tag, lo he llamado ‘CT – Write product sales data to a BigQuery table’ (CT son las siglas de ‘Custom Template):
Este tag lee la información que hay en el ‘event object’ que ha generado tu cliente de GA4 (la venta que se ha producido en tu ‘ecommerce’). Al ejecutarse, interactúa con la API de BigQuery y escribe en la tabla correspondiente la información de la transacción. Si te fijas, he dejado el campo ‘Google Cloud Platform project containing BigQuery dataset and table’ en blanco. Si tu contenedor de GTM server-side y tu dataset de BigQuery pertenecen al mismo proyecto (como es mi caso), este campo es opcional, no tienes que rellenarlo.
La implementación en funcionamiento, de principio a fin
Venga, ahora te voy a mostrar cómo funciona todo de principio a fin. Por si te ayuda, este el orden en el que sucede todo:
- Un usuario realiza una compra en tu ‘ecommerce’. La transacción se refleja en tu dataLayer.
- Tu contenedor web de Google Tag Manager entra en acción: un tag de GA4 envía una petición por http a tu contenedor de GTM server-side con la información expuesta en el dataLayer.
- Un cliente de GA4 intercepta este request entrante a tu contenedor de GTM server-side y genera un ‘event object’ con él.
- Un tag interactúa con la API de BigQuery y graba en la tabla seleccionada la información correspondiente a la compra de tu tienda online.
Fíjate en la secuencia de los acontecimientos:
dataLayer.push()
se ejecuta desde mi contenedor de GTM cliente. Puedes seguir esta senda, o pedirle a tu equipo de Desarrollo que ejecute este dataLayer.push()
cuando se tramite una compra en tu ‘ecommerce’ Reflexiones finales
Google Tag Manager (GTM) tiene mucho potencial para interactuar con otras API’s y convertirse así en mucho más que un gestor de etiquetas. El ejercicio que te muestro en este post es una prueba de concepto, pero espero te sirva para darte cuenta de lo que puedes conseguir interactuando con la API de BigQuery: generar una base de datos que luego explotar para consumir tu propia analítica. Y todo sin pasar por Google Analytics.
Es cierto que uso un flujo de datos de GA4 para enviar información a mi contenedor de GTM server-side. Esto supone que mi servidor de tagueo podrá servir cookies por http en mi navegador, pero incluso en este caso, la información no viajará a los servidores de Google Analytics. GTM server-side actúa como un ‘proxy’: recibe una petición y la deriva.
En cualquier caso, para desarrollar una implementación totalmente ‘cookieless’ lo idóneo quizá hubiese sido seguir otra senda para enviar la información al ‘tagging server’, como por ejemplo usar el Fetch API