When previewing Custom HTML tags in Google Tag Manager you’ve almost certainly run into a situation where the GTM variable shows up as a weird JavaScript method resembling something like this:


And this is when you were expecting it to show the actual, resolved value!

It doesn’t help that every now and then the preview mode actually shows to correct value in the preview mode.

What’s up with that? Well, there’s a fairly logical explanation to this. Read on!


The Simmer Newsletter

Subscribe to the Simmer newsletter to get the latest news and content from Simo Ahava into your email inbox!

Tip 105: The .macro() call in Custom HTML tags

Let’s start with the good news.

Your tag and the variables are most likely working as they should.

When you create a Custom HTML tag in Google Tag Manager, you are actually creating HTML elements that are appended to the end of the <body> element when the tag fires.

This means that if the HTML element you are injecting is a <script></script> block, i.e. a JavaScript execution context, the code isn’t run until 1) the HTML element is appended to the end of <body>, and 2) the browser has rendered the element and is ready to execute the JavaScript within the <script> block.

Now, when you add a Google Tag Manager variable to a Custom HTML tag, you use the {{variableName}} syntax.

Because it would make little sense to resolve the variable before the surrounding JavaScript code is run, what follows is that GTM does not resolve the variables until the Custom HTML tag has been injected to the end of <body>.

To be able to defer the variables to wait for injection, this must mean that the variables themselves must be some type of function calls that the surrounding script will execute and resolve once the tag has been injected.

Enter google_tag_manager['GTM-ABCD123'].macro(n)!

Every single Google Tag Manager used in a Custom HTML tag will receive its own, unique value for n, which will then be passed to the .macro() method when the tag code is executed.

Note! This is not a deterministic value. The n can change from container version to another, so you should NOT build any sort of logic around having some specific variable always assigned some specific value for n.

So why doesn’t it resolve in Preview mode?

First of all, this is a good question. Google Tag Manager shows you what the call to macro() looks like and doesn’t actually replace the call with the value of the variable at the time of inspecting the tag.

The most likely reason why GTM doesn’t resolve the macro() call is because it would have to resolve the entire Custom HTML tag content to be able to adjust the value returned by macro() depending on context.

And this makes no sense - GTM can’t start resolving Custom HTML tags within the Preview mode window - how would it render things like HTML elements? How would it resolve things like document.getElementById() when the iframe has no access to the actual page itself?

So GTM can’t resolve the Custom HTML tag while in Preview mode, which is why it shows you the pre-injection status of the Custom HTML tag, together with the .macro() calls and all.

It’s only after the Custom HTML tag is added to the page that the .macro() calls are executed. This bears repeating since it’s an important point.

But why do SOME variables resolve in Preview mode?

Hahaa! You found the exception.

Take a look at this Custom HTML tag:

  (function() {
    console.log({{Container ID}});
    console.log("{{Container ID}}");

See the difference? The first logs the variable plainly, without regard to what its return type is. The second casts the variable as a string before logging it.

Let’s take a look at what Preview mode shows.

Fascinating! Why does casting the variable as a string force GTM to resolve the variable?

Simple. Imagine if GTM did NOT resolve the .macro() call. The second line would become:


In other words, the whole function call would be cast into a string, which would end up writing google_tag_manager['GTM-ABCD123'].macro(15) into the browser console rather than GTM-ABCD123!

So when you cast the variable into a string, GTM will resolve it pre-injection.

How do I know it’s working?

Because Google Tag Manager shows variables as macro() calls, it’s really difficult to quickly check if the Custom HTML tag nevertheless works. However, here are some tips:

1. Check the value of the variable in the Variables tab

This is an easy way to get a good idea if the variable works or not. You know what the variable should be (e.g. “Container ID” in my case), so all you have to do is select the trigger event that fires the Custom HTML tag, open the Variables tab in Preview mode, and check the value of the variable.

The variable value should match the one in the injected Custom HTML tag.

2. Add some console.log() calls

Another slightly awkward way to debug is to add console.log() calls to the Custom HTML tag. Have the variable value log into console just before you use it to verify it actually has the right value.

  (function() {
    var log = function(msg) {
      if ({{Debug Mode}}) {

    log({{Some Variable}});
    window.someFunction({{Some Variable}});

In the code sample above, there is a global method window.someFunction to which we want to pass the variable {{Some Variable}}. However, we are uncertain what its actual value is since Preview mode isn’t very helpful.

Because of this, we log the value of {{Some Variable}} into the browser JavaScript console.

Furthermore, I’ve built a helper function named log() that only logs the value to console if GTM is in Preview mode. This way you avoid your tags from writing arbitrary log messages into the live browser console.

3. Debug the tag result

This is the most difficult way to debug whether or not the variable worked. Unfortunately, it’s also the best way to do it.

When you add JavaScript to a Custom HTML tag, it should do something. Maybe it adds some text to an HTML element, maybe it creates a new pixel or Ajax call, or perhaps it pushes something to dataLayer.

In any case, the best way to ensure whether or not the .macro() call resolved to its correct value is to check the output or result of the tag itself to see if whatever value the variable was supposed to pass was done correctly.

For example, let’s say your tag creates a pixel request like this:

  (function() {
    var imgId = '?id=' + {{Container ID}};
    var imgUrl = 'https://www.somedomain.com/img' + imgId;
    var el = document.createElement('img');
    el.src = imgUrl;

It’s a simple, almost nonsensical example, but it should serve to highlight the debug process nicely.

The tag creates a new image element whose URL is https://www.somedomain.com/img plus the string ?id= concatenated with whatever is returned by the {{Container ID}} variable.

This is what it looks like in Preview mode:

This is what it looks like after injection in the Inspect Elements pane:

Note! As you can see, in Inspect Elements the script element still has the .macro() call. This is how the browser works. GTM injected an element with the .macro() call and this is what was added to the page. If you remember, the script isn’t executed until AFTER injection.

Finally, you can inspect the Network Requests to find the image request with the ID in place.

Also, because the tag did its own little injection with document.body.appendChild(el), you can find the image element at the end of <body> by inspecting the elements on the page.


I hope this article has alleviated your concerns. If you see a call to .macro() in Google Tag Manager’s Preview mode, don’t worry. It’s what you’re supposed to see.

GTM will not resolve variables until the Custom HTML tag has been injected at the end of <body> and the browser has begun to render any JavaScript between <script> and </script> within the Custom HTML tag.

The main exception is variables cast into strings, which Google Tag Manager will have to resolve before injection or else it will end up casting the function expression itself into a string which is not helpful.

Hopefully this article is helpful in offering you a number of ways you can debug those pesky .macro() calls just to be sure that things are working as they should.

Let me know if you’ve run into edge cases where it still doesn’t work, or if you have other examples in addition to cast strings where GTM resolves the variable in Preview mode.