GTM Pageview Triggers: Firing Tags at the Right Moment

A page does not load in a single instant. Learn GTM’s three pageview triggers, Page View, DOM Ready, and Window Loaded, and how to fire every tag at the right moment.

Most of what GTM does comes down to one question: when should this tag fire? For the largest category of tags, the analytics hits, the ad pixels, the conversion tracking, the answer is tied to the page loading. And here is the part many people never internalize: a page does not “load” at a single instant. It loads in stages, and GTM gives you a separate trigger for each one. Picking the wrong stage is the difference between a tag that works flawlessly and one that throws null errors or quietly fires too late to matter.

The mental model worth holding in your head is three milestones in the page’s life. First, the HTML response begins arriving and the GTM container starts executing. A moment later, the HTML has been fully parsed and the DOM tree exists, so elements can be queried safely. Finally, every last image, font, stylesheet, and async script finishes, and the page is fully rendered. The three pageview triggers map exactly onto these three moments, and on every page load they fire in that order.

The Three Pageview Triggers

Page View fires first, on GTM’s gtm.js event, the instant the container script begins executing. It is the fastest trigger available and it fires before any DOM elements are guaranteed to exist. That is exactly what you want for a tag that does not need to read the page and simply needs to send a hit as early as possible: a GA4 pageview, a Meta Pixel base code, a Google Ads remarketing tag.

DOM Ready fires second, on gtm.dom, which corresponds to the browser’s DOMContentLoaded event. By now the HTML is fully parsed and the DOM tree is complete, but images and iframes may still be loading. This is the trigger for any tag that reads or interacts with page elements: querying a data-* attribute, looking up an element’s text, counting items on the page. Fire that work on Page View and the elements may not exist yet.

Window Loaded fires last, on gtm.load, the equivalent of window.onload. It waits until the page is completely rendered, every image and async script included. It is the slowest trigger, and you reserve it for tags that genuinely need the finished page: performance and Core Web Vitals measurement, full-page heatmap snapshots, anything that depends on the final rendered state.

The rule of thumb writes itself. Start with Page View. Move to DOM Ready only if your tag needs to read the DOM. Move to Window Loaded only if you truly need everything loaded. Each step later is a step slower, and slower means a higher chance of missing data if the user navigates away before the tag fires.

Choosing in Practice

The decision is a short walk down a single question. Does the tag need to read DOM elements? If no, use Page View, which covers the vast majority of analytics and advertising tags. If yes, ask a follow-up: does it also need images and async resources to be present? If no, DOM Ready is right; if yes, Window Loaded.

Concretely, a GA4 page_view, a Meta Pixel, and a Google Ads remarketing tag all belong on Page View. A tag that reads product data out of the DOM or sets up element-visibility tracking belongs on DOM Ready. A Core Web Vitals script or a heatmap tool taking full-page snapshots belongs on Window Loaded. The pattern is consistent: the more of the page the tag depends on, the later it has to wait.

Configuring a Trigger

In the GTM interface you go to Triggers, then New, then pick the type. A site-wide pageview trigger is as simple as it gets:

Name: Pageview - All Pages
Trigger Type: Page View
This trigger fires on: All Page Views

The name should describe both the type and the scope so the next person understands it at a glance, something like DOM Ready - Checkout. To narrow a trigger to specific pages, switch from “All Page Views” to “Some Page Views,” which unlocks the condition rows:

This trigger fires on: Some Page Views
Conditions:
Page Path contains /thank-you

Page Path is a built-in variable holding just the URL path with no domain, and contains does a forgiving substring match, which is usually safer than equals because it survives trailing slashes and minor path variations.

The Variables and Operators You Filter On

A handful of built-in variables cover almost all page targeting. Page URL gives the full address including query string, Page Hostname gives the domain alone, Page Path gives just the path, Page Path + Query adds the query string back to the path, and Referrer holds the previous page’s URL. For path-based logic you almost always want Page Path rather than Page URL, because filtering on the full URL means a stray query parameter like a coupon code or a UTM tag can break an otherwise correct match.

The operators range from strict to flexible. equals demands an exact match, useful for isolating the homepage with Page Path equals /contains matches a substring anywhere, starts with and ends with match prefixes and suffixes, and matches RegEx handles the genuinely complex cases, such as ^/products/[0-9]+$ for numeric product pages. Each also has a negated form (does not containdoes not equal), which is what you reach for when building trigger exceptions.

Scenarios That Come Up Constantly

The GA4 configuration tag is the canonical Page View case: fire as early as possible, on every page, because analytics needs to be running before the user can do anything worth tracking.

A conversion pixel on a thank-you page is the canonical filtered case, and it carries an important lesson about hostnames:

Name: Pageview - Thank You Page
Trigger Type: Page View
This trigger fires on: Some Page Views
Conditions:
Page Path contains /thank-you
Page Hostname equals www.example.com

The path condition targets the success page; the hostname condition is what keeps the tag from firing on your staging and development environments, which share the same paths but different domains. Leaving the hostname off is how test purchases end up inflating production conversion data.

Reading product data needs the DOM, so it goes on DOM Ready filtered to product pages with Page Path starts with /product/. A performance script needs the fully rendered page, so it goes on Window Loaded across all pages. The trigger type follows directly from what the tag has to touch.

Filtering on More Than the URL

Pageview triggers are not limited to URL conditions; they can test any variable. To fire a tag only for logged-in users, you filter on a dataLayer value:

Trigger Type: DOM Ready
This trigger fires on: Some DOM Ready Events
Conditions:
{{User Status}} equals logged_in

There is a timing catch here worth stating plainly. For this to work, the User Status value must already be in the dataLayer before the trigger evaluates. For a DOM Ready trigger, that means it has to be pushed in the page <head> ahead of GTM, or at least before the gtm.dom event fires. If the value arrives after the trigger checks for it, the condition sees undefined and the tag silently does not fire, which is one of the most common “but it works on my machine” bugs in GTM.

Excluding internal traffic works the other direction, as a trigger exception added to the tag rather than a condition on the trigger. You attach a Page View exception that fires when {{Is Internal IP}} equals true, and the tag skips your own team’s visits.

Single-Page Apps Break the Model

Here is where the three-milestone model meets its limit. A React, Vue, or Next.js app with client-side routing fires the real Page View event exactly once, on the very first document load. Every “page change” after that swaps content without reloading the document, so Page View, DOM Ready, and Window Loaded all stay silent. To the standard pageview triggers, the user visited one page and stopped.

The fix is the History Change trigger, which fires when the URL changes through the History API without a reload:

Trigger Type: History Change
This trigger fires on: All History Changes

It exposes variables like History Source (whether the change came from pushStatepopstate, or a hash change) and the old and new URL fragments. The clean way to think about it is a division of labor: Page View fires on the first load and never again, History Change fires on every subsequent route change and never on the first. So a properly tracked SPA usually needs both, a Page View trigger for the initial load and a History Change trigger for the navigations after it. This is the trigger-level counterpart to the dataLayer approach covered in my guide to single page application tracking, and on a complex app you often combine both techniques.

A Full Walkthrough: GA4 Pageviews

Putting it together for a baseline GA4 setup takes four steps. First, create the trigger as a plain Page View firing on All Page Views, named Pageview - All Pages. Second, create the GA4 configuration tag:

Name: GA4 - Configuration
Tag Type: Google Analytics: GA4 Configuration
Measurement ID: {{Const - GA4 Measurement ID}}
Send a page view event when this configuration loads: enabled
Triggering: Pageview - All Pages

Holding the measurement ID in a Constant variable rather than typing G-XXXXXXXXXX directly means one place to update it later, and the “send a page view event” checkbox makes GA4 emit the page_view automatically when the tag fires.

Third, verify before trusting. Enter Preview mode, load the site, and on the first “Container Loaded” event confirm both the trigger and the tag fired in Tag Assistant. Then open GA4’s Realtime report to see the pageview appear within about thirty seconds, and DebugView to confirm the page_view event arrived with its parameters. Fourth, publish with a descriptive version name like 2026-05-01 - GA4 baseline pageview, so that if something breaks later, your version history tells the story.

The Pitfalls Everyone Hits

A few failure modes recur often enough to memorize. A tag that reads the DOM but sits on a Page View trigger throws “cannot read property of null” errors, because the elements do not exist yet; the fix is DOM Ready. A tag on Window Loaded behind a page full of slow images fires very late or never; unless you genuinely need the full load, move it to DOM Ready. A URL filter using equals against a path that has a trailing slash never matches; use contains or normalize with a regex.

Filtering on Page URL when query parameters vary causes inconsistent firing, so filter on Page Path for path logic. A typo in a “Some Page Views” path condition makes the tag silently never fire, which is exactly why every new trigger gets tested in Preview before publishing. And a tag that depends on a dataLayer value not yet pushed will see undefined; when timing is the problem, the right answer is usually a Custom Event trigger fired by the dataLayer push itself, rather than a pageview trigger racing against the data.

In Conclusion

Pageview triggers fire tags at one of three moments in the load lifecycle: Page View at the start, DOM Ready once the DOM exists, Window Loaded once everything has rendered. Default to Page View for speed, escalate to DOM Ready only when the tag reads the page, and reserve Window Loaded for tags that need the fully rendered result. Filter on Page Path rather than Page URL, always add a hostname condition on production tags to keep staging data out, and remember that single-page apps need a History Change trigger because the standard three fire only once. Name every trigger for its type and scope, test it in Preview, and the largest and most important category of tags in your container will fire exactly when it should.

View Comments (1)

Leave a Reply

Subscribe to My Newsletter

Subscribe to my email newsletter to get the latest posts delivered right to your email. Pure inspiration, zero spam.

Discover more from Discuss Data Science, Machine Learning and Analytics

Subscribe now to keep reading and get access to the full archive.

Continue reading