Lessons in this module
Lesson 13 of 15
Tab & Window Visibility
① Connect your GTM container
Paste your container ID to load it into this page. It only ever runs here.
Advanced: use a specific environment
A page can be open without being watched. The visitor switches to another tab, or clicks a different window, and your "time on page" quietly counts minutes nobody spent reading. Accurate engagement tracking means knowing when the page is actually visible and focused.
The browser exposes two signals for this: the Page Visibility API (visibilitychange, for tab switches) and window focus/blur (for clicking another window or app). GTM has no built-in trigger for either, so a Custom HTML listener covers both and pushes a single visibility_change event with the source and new state, which you catch with a Custom Event trigger.
Goal
Add a visibility listener, catch its visibility_change push with a Custom Event trigger, and capture the state in Data Layer Variables.
Build it in GTM
Add the visibility listener
GTM has no built-in trigger for this, so a small Custom HTML tag does the listening and pushes its own event. This one script covers two different signals:
- Tab visibility — the Page Visibility API (
visibilitychange/document.visibilityState) fires when the tab is switched away or the window is minimized. - Window focus — the window's
focusandblurevents fire when another window or app comes forward, even while this page stays on screen.
- Go to Tags → New and choose Custom HTML.
- Paste the script below into the HTML field. It pushes
visibility_changewith avisibility_source(taborwindow) plus the current state. - Set its firing trigger to All Pages, name it
Listener - Visibility, and Save.
<script> (function () { window.dataLayer = window.dataLayer || []; function pushVisibility(source) { window.dataLayer.push({ event: 'visibility_change', visibility_source: source, // 'tab' or 'window' page_visible: document.visibilityState === 'visible', // tab on screen? window_focused: document.hasFocus() // window in front? }); } // Tab visibility: switching tabs or minimizing the window. document.addEventListener('visibilitychange', function () { pushVisibility('tab'); }); // Window focus: clicking another window or app, even if this page // stays visible. Lets you test without opening a second tab. window.addEventListener('focus', function () { pushVisibility('window'); }); window.addEventListener('blur', function () { pushVisibility('window'); }); })(); </script>- Tab visibility — the Page Visibility API (
Create the Custom Event trigger
- Go to Triggers → New and choose Custom Event.
- Set Event name to
visibility_change(it must match theeventyou pushed). - Name it
Event - visibility_changeand Save.
Capture the state in variables
Add a Data Layer Variable (a User-Defined variable) for each field you pushed so your tag can report which signal changed and its new state:
- Go to Variables → New (under User-Defined Variables) and choose Data Layer Variable.
- Create
DLV - visibility_source(namevisibility_source),DLV - page_visible(namepage_visible), andDLV - window_focused(namewindow_focused). - Save each one.
Attach a tag
- Go to Tags → New and choose Custom HTML with
<script></script>(or your GA4 Event tag). Name itCustom HTML - Test. - Under Triggering, add
Event - visibility_change. If it's a GA4 tag, sendDLV - visibility_source,DLV - page_visible, andDLV - window_focusedas parameters. Then Save.
- Go to Tags → New and choose Custom HTML with
Debug in Tag Assistant
Copy this lesson's live URL and paste it into GTM Preview, that is the page Tag Assistant connects to. It has the clickable elements, so this page stays clean for reading.
- In your GTM, click Preview.
- Paste the live URL above and click Connect.
- Interact with the live page and watch your tag fire in Tag Assistant.
What you should expect to see
visibility_change event appears in the timeline and dataLayer with visibility_source set to tab or window, the matching state, and your tag fires.Verify your container
Built it? Export your container, Admin → Export Container, choose your workspace, then drop the JSON here to check it against this lesson.
Drop your container .json here
or browse · checked in your browser, nothing is uploaded