Ezoic Ads has quite a success story and became quickly the Monetization-Blogosphere’s darling. With AI to boost your ad moneys from websites you build mainly for monetization reasons. There are all the great reviews, showcases and stories you’ll find on the web. Like on nichepursuits.com where it all started for me, when I first heard of Ezoic. Quickly I realized there is one major concern I have with AI in general that just bubbled up when I implemented Ezoic the first time in a WordPress blog:
- AI is intransparent in its decisions and outcome (in this case of automized ad display, as every client will have a different amount of ads displayed, and therefor we have no clue what exactly is the result)
Unfortunately the human nature (at least this applies to myself) is curios. I need to know what why and how something happened. Can you just rely on the Ezoic systems that there was „just the right“ amount of ads displayed to your users? How many ads were displayed per user on a single page? Which ad-spaces were empty and which not? Is my content overcrowded with ads?
Lets find out. By implementing some code and track the s**t out of all the ads, that are displayed to my websites visitors 🙂
(no, not on this page though; no moneys for me here)
How many ads are optimal?
Lets first dig into the actual motivation on this post. It’s not all just about the knowledge how many ads my users see. Sure, thats really interesting to know, but whats the business case?
To answer the question of optimal ads per user or per page, you will quickly realize its not 42. No. Not even a certain number. Its a rather a rate of number of ads per user per time. Often a blogger will draw a line of 6, 4 or some other Number which they feel comfortable with. However thats too much of personal bias and so on. Which I found myself in, when I first thought about adding display ads to one of my blogs. Users mustn’t see too many ads and must be able to access the content first. As I put a crazy amount of time and effort to create content.

There actually is a huge post on this whole optimal ad count topic on Ezoic’s blog. Yes, it is actually a very good read with lots of insights that is way more than a „lets put some thin content so we have something“-blogpost: How Many Ads Are Too Many?
And with this I’ll close this little side-topic without digging deeper. Lets focus on the main question at hand.
How can I track the amount of ads displayed with Ezoic in GTM?
With Google Tag Manager (GTM) there is a little framework I installed to first analyze the amount, size and meta of ads displayed on a page to a client. This first check creates a „report“ and pushes all the information back to my Data Layer. Second I populate all the information to Google Analytics (GA) via Enhanced Ecommerce Tracking and Internal Promotion Tracking. This way I take advantage of native reports in the GA web interface (well, if you actually want to use this…). But even better: this includes a batching of all the data into a single request. As we will see, the amount of data tracked can quickly explode here, and we do wan’t batches. Right?
Very First: Challenges and Caveats
Now before we get into the code. You must know this. We will built a code that is prone to fail, as we anchor all our information on CSS selectors and HTML DOM information that Ezoic dynamically renders into a page. There are certain templates Ezoics code uses to embed the ads. But those will change as they update and improve their templates. And then this GTM code will fail. I have no clue when this will happen and how much effort there will be to maintain this code in GTM. I’ll keep you updated though, as I see something break in my data and code.
There is also one more challenge we will have to face. The dynamic nature of ads that Ezoic beds into your pages. Not all ads are displayed exactly on page load, ready or any other state. Even after some time the ads are reloaded, updated and empty spaces are filled and filled spaces are emptied. This challenges our tracking, but we’ll get to this in a moment.
First: Analyze the amount of ads displayed
Create a HTML tag in your GTM. The following code will analyze a pages DOM and look for ezoic ads. All ad spaces that are found are populated into an array of objects. With this properties:
- index: a simple counter per ad space
- width: the HTML objects property „scrollWidth“ which tells us the actual size
- height: same as width, you get it
- placement: the HTML objects property from the dataset directly on exactly the object found
- comments: this contents all the HTML comment information from the object found. Including the objects parents comments and all further comments inheritated. This is implemented because Ezoic facilitates HTML comments to push meta information on ads that is very interesting and useful.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
//<script> (function(){ function filterNone(){ return NodeFilter.FILTER_ACCEPT; }; var ads = document.querySelectorAll({{static.ezoicAdsCssSelector}}) var totalAds = []; var totalAdsCounter = 0; ads.forEach(function(elem, i){ var adFrame = elem.querySelector("iframe") var comments = []; var nodeIterator = document.createNodeIterator(elem.parentNode, NodeFilter.SHOW_COMMENT, filterNone, false); while (curNode = nodeIterator.nextNode()) { comments.push(curNode.nodeValue); } if(adFrame && adFrame.width){ totalAdsCounter += adFrame.width > 0 ? 1 : 0; totalAds.push({ index: i, width: adFrame.scrollWidth, height: adFrame.scrollHeight, placement: elem.dataset.ezName, comments:comments.join("; ") }); } else totalAds.push({ index: i, width: 0, height: 0, placement: "empty", comments:comments.join("; ") }); }) window.dataLayer = window.dataLayer || [] window.dataLayer.push({ event: "ezoic.adimpression", ezoic: { adimpression: totalAds, submittedAdCounter: totalAdsCounter } }) })() //</script> |
Second: React and Read the Data
To facilitate the data we got from the „Transparancy Report“ HTML tag we need some basic „groundwork“ GTM variables.
Basic Variables (Data Layer Variables):
- dataLayer.ezoic.adImpression (simply map this Data Layer Var to „ezoic.adimpression“ which is populated in the tag above)
- dataLayer.ezoic.submittedAdsCounter (simply map this Data Layer Var to „ezoic.submittedAdCounter“)
- static.ezoicAdsCssSelector: This is the „core“ item which is used to identify ad placements from Ezoic. Prone to fail, as this will change over time. As of now (2020-08-05) the content is: [id*=ezoic-pub]
More complex Variables:
These three JS Variablers we will need in a second, just bear with me.
JS Variable Name: „js.ezoicEecPromotionImpression“
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function(){ var allAds = {{dl.ezoic.adimpression}}; var eec = {}; var ads = []; if(allAds && Array.isArray(allAds)){ allAds.forEach(function(ad){ ads.push({ 'id': ad.index+"<em>by</em>"+ad.width+"x"+ad.height+"_"+ad.placement, 'name': ad.comments.replace(/\s+/g, ''), 'creative': ad.width+"x"+ad.height, 'position': ad.index }); }); eec = { 'ecommerce': { 'promoView': { 'promotions': ads } } } } return eec; } |
(this will create an object that is ready to use in GA’s EEC tracking)
JS Variable Name:Â js.ezoicVisibleAdsCounter
1 2 3 4 5 6 7 8 9 |
function(){ var ads = document.querySelectorAll({{static.ezoicAdsCssSelector}}) var r = 0; ads.forEach(function(elem, i){ var adFrame = elem.querySelector("iframe"); r += adFrame && adFrame.width && adFrame.width > 0 ? 1 : 0; }); return r; } |
(this will use the same CSS selector as the main ‚Transparancy Report‘ HTML tag and simply count the visible ads)
JS Variable Name: js.ezoicAdUpdated
1 2 3 |
function(){ return {{js.ezoicVisibleAdsCounter}} > {{dl.ezoicSubmittedAdsCounter}} ? true : false; } |
(this returns whether the count of visible ads is greater than the last known counter when ad-impressions were submitted to GA)
The Timing Challenge
As already mentioned there is a major challenge that we still need to tackle. Brace up, as we discuss some ideas on how to overcome this. The ever so dynamic nature of Ezoic ads. Which just can’t stay still and change in their size and content, after load and from second to second as the client browses a page.
Mainly because of the GTMs queueing system, we interact in loops of events that just so happen all the time. Page loaded. DOM ready. A click, a scroll and so on. But there is no single clear event on when an ad is loaded, displayed or updated. So we again have to build some adapter workaround countermeasure, which again comes with all the disclaimer. One more time I ask you to build error prone code.

There are two options I see to work around this issue.
- Facilitate mutation observer events: use the CSS selector we already have to facilitate mutation observers as an API to trigger the ‚Transparancy Report‘ tag.
- Use static triggers and events you already have and check if Ezoic ads changed between two events.
The first approach is much more detailed and will give you the most insights. However this comes with the cost of added complexity (adding hazard of shaky code), browsers compatibility issues and performance that will increase the load on your clients browser. Therefor I didn’t implement any such solution and will showcase just the latter approach.
Instead I use the following system to trigger my Ezoic ads information loop. So the main GTM Tag to analyze and report what ads are displayed has two triggers actually.
a)Â Some pretty early event (such as Pageview, DOM Ready,…) where hopefully some ads are already visible.
b) Any event, later then the first triggering event, where the amount of visible Ads changed.
This is as tricky as its vague in description. The reason is, that the very first ads will probably load way later than the actual page is displayed to the user. Therefor we need the variable, which simply gives us an Integer counter of how many ads are visible at this exact event in our GTM queue (dataLayer). The first (and initial) transparancy report mustn’t start prior this counter is >0. Otherwise we will bloat our GA data with empty-ad-space information. Though maybe its also helpful, so it’s also possible to just track an initial status (without considering the counter). This is absolutely open for discussion and to be decided per individual case.
Then there is this second trigger, which is very vague described as „any event, where ad count changed“. This setup will only work, if you already know there are more events to come on every relevant pageview. Or you’ll have to implement a timer trigger or similar.
In my case there actually is a 10sec trigger already installed to track basic interaction rates with my blogs content. Just a short check if the user already dropped out or is still there, reading the content. Also there are more clicks and interactions tracked, so I generally assume there are „enough“ events to be queued and trigger my Transparancy Report tag. This is the actual trigger:

And thats all the workaround to give an educated guess what goes on with my page and all the ads. Now what to do with all this data?
Finalize with a pinch of EEC Tracking
The data streams in the ad information pipeline. As the pages load and with every event the system checks for changes and reports if something happened. If so we receive dataLayer updates. With an array full of ad display objects and properties. Now arrays with multiple objects are a multidimensional thing but GA in its basic setup only expects two dimensional observations („a [pageview|session|event] happend with the following properties and values“). EEC to the rescue!
Fortunately around 2014 already GA took care of this. With enhanced ecommerce and the option to fill index based observations into single dimensions (like a list of products or internal promotions). Which is exactly what we need to track multiple ad observations on a „flat“ single pageview interaction.
Therefor I created a little translation adapter script, which will take the Ezoic ad information and parse it into the EEC internal promotion JSON representation. This is the GTM variables code (custom JS variable): „js.ezoicEecPromotionImpression“ (see above)

Now what will this look like in GA when all this GTM code is published onto production?

Summary
In this post I created the following steps to analyze the ad exposure to website clients with GTM.
- Create the CSS selector based HTML/JS tag, which check the page for ads. These observations are pushed into the dataLayer.
- Create meaningful triggers that work for your case, where I only described some options and caveats.
- React to the transparancy reports from the first step and send the data to GA, e.g. with GA enhanced ecommerce and internal promotions.