December 01, 2023

dataLayer Proxy: How to modify events before Google Tag Manager processes them

by Justus

Have you ever found yourself lacking some important piece of information missing from your dataLayer events?
Something that you know how to get, but you don’t have easy access to the code that pushes the event to the dataLayer?

This is where a dataLayer proxy comes in handy.
It allows you to modify events as they are pushed to the dataLayer, but before they are processed by Google Tag Manager.

The DataLayerProxy JavaScript class below is an easy way to add one or several modifications.

Here are two examples of modifications that you could add with the proxy:

const dLProxy = new DataLayerProxy();

// This modification adds a unique identifier to each event. 
// We use the crypto.randomUUID() function to generate a unique ID for each event.
dLProxy.addEventModification(event => {
	event.event_id = crypto.randomUUID();
	return event;

// This modification adds a timestamp to each event. 
// We use the new Date().toISOString() function to get the current date and time in the ISO 8601 format.
dLProxy.addEventModification(event => {
	event.timestamp = new Date().toISOString();
	return event;

As you can see both when checking dataLayer in your browser’s JS console or in GTM’s Preview Mode, the modifications are applied:

Screenshot of Google Tag Manager Preview mode showing an example dataLayer event with an event_id that was attached through the modification described above

All you need beforehand is adding the following code to your website just after your Google Tag Manager container snippet and before any other code that pushes events to the dataLayer.

class DataLayerProxy {
	constructor() {
		this.dataLayer = window.dataLayer || [];
		this.modifications = [];

		const handler = {
			apply: (target, thisArg, argumentsList) => {
				for (let i = 0; i < argumentsList.length; i++) {
					// Check if it's a regular dataLayer event
					if (typeof argumentsList[i] === 'object' && argumentsList[i] !== null && 'event' in argumentsList[i]) {
						this.modifications.forEach(modification => {
							argumentsList[i] = modification(argumentsList[i]);
					// Check if it's a gtag event
					else if (typeof argumentsList[i] === 'object' && argumentsList[i].length > 2 && typeof argumentsList[i][2] === 'object') {
						this.modifications.forEach(modification => {
							argumentsList[i][2] = modification(argumentsList[i][2]);
					// Check if it's a custom template event
					else if (typeof argumentsList[i] === 'object' && 'h' in argumentsList[i] && typeof argumentsList[i].h === 'object' && 'event' in argumentsList[i].h) {
						this.modifications.forEach(modification => {
							argumentsList[i].h = modification(argumentsList[i].h);
				return target.apply(thisArg, argumentsList);

		this.dataLayer.push = new Proxy(this.dataLayer.push, handler);

	addEventModification(modification) {

Event sources

The proxy supports three different ways of events pushed to dataLayer:

  1. Regular dataLayer.push() events, including multiple events in one push.
  2. gtag() events
  3. Events pushed through a custom template via the createQueue(“dataLayer”) API

Use as Custom HTML tag

While not impossible, I’d recommend against using the proxy as a Custom HTML tag in Google Tag Manager.
The proxy would need to be loaded not just before any other tags but also before any dataLayer events are actually pushed.
On many websites, events are pushed much earlier than the GTM container snippet is done loading and the Custom HTML tag is executed. Those early events would not be modified by the proxy.

If you insist though, use a transpiler like Babel to convert the class to ES5, because GTM’s Custom HTML tags don’t support modern JavaScript.

Should I use this?

If you have access to the code and/or developer pushing events to the dataLayer, you should probably modify that code rather than monkey-patching events after the fact like this.
But if you know what you’re doing and enjoy a good workaround, go for it.

Thank you

Thanks to Simo Ahava for his input on this matter – who would have thought how many ways there are to push stuff into dataLayer?
If you’re interested in topics like these, consider joining Measure Slack, a great community of analytics professionals.

Did this article help you?

Then follow me on twitter
so you won't miss new articles here and for Web Analytics and Marketing technology updates in general :)