Skip to content

Guide 113 · Container · GA4 + Tag Manager

Build a history change trigger: fire a tag when a single-page app changes view

Checked 11 November 2026 · Verification: container fragment validated structurally (12 checks, all passed) · Steps traced to Tag Manager Help, opened 11 November 2026 · 5-minute read

A normal page view fires when the browser loads a page. A single-page app loads once and then swaps views by changing the browser history, so no new page view ever fires — and tags on All Pages fire once for the whole session. A history change trigger fires when the history changes, which is how the app says it moved to a new view. Google's trigger page lists the History Change trigger, and the built-in variables page lists History Source, which reads gtm.historyChangeSource and names the call that caused the change: pushState, replaceState or popState. The worked example measures a four-view app.

Expected signal

After step 3, Preview mode shows the tag under Tags Fired once per view change — moving through all 4 views of the worked app fires it 4 times, with History Source reading pushState on each step.

Download the container fragment: the history change trigger fragment (JSON) (export format version 2, 1124 bytes, MD5 dce4c5f5c99443af232b2812837e8a8c). It holds the history change trigger and a GA4 event tag that fires on it. The file is a hand-built import template, validated structurally — it was not exported from a live container.

What you need

  • A single-page app that changes views without reloading. The worked example is an app with 4 views. A normal multi-page site does not need this trigger — every view is a page load there.
  • History Source enabled. The filter can only tell pushState from popState apart if the History Source built-in is enabled.
  • Edit access to the container. Creating a trigger is a container edit.

The trace

  1. Go to Triggers and click New. Triggers live in their own list in the left navigation. Click New and name the trigger History Change - SPA views.
  2. Click Trigger Configuration and choose History Change. The trigger has no threshold to set: it fires on every history change the browser reports. The three sources are pushState, when the app adds a view to the history; replaceState, when it swaps the current entry; and popState, when the visitor goes back or forward.
  3. Attach the trigger to the tag and save both. Open the GA4 event tag, click Triggering, pick the new trigger, and save. Moving through the worked app's 4 views fires the tag 4 times — 1 per view change, 4 in all. The tag sends the new page path with each firing, so the four firings read as four different views instead of one long page view.

Dead ends

DEAD END — HISTORY CHANGE ON A NORMAL SITE. On a multi-page site every view change is a page load, and the browser history changes with it. A history change trigger there fires on top of the page view trigger, and the same visit gets counted twice — once as a page view, once as a history change.

DEAD END — HISTORY REPLACEMENT ON EVERY VIEW. Some apps call replaceState on every view to update the address without adding a history entry. A trigger that counts history changes as views then counts those updates too. Filter on History Source if the app mixes pushState and replaceState and only the pushes are views.

DEAD END — GOING BACK COUNTS AS A NEW VIEW. popState fires when the visitor goes back or forward, which revisits a view rather than opening a new one. Counting every popState as a fresh view inflates the view count — the visitor saw 4 views, went back, and the count reads 5.

End state

One history change trigger firing once per view change in the single-page app, attached to one GA4 event tag that reports the new path. The app's views are now measured the way a multi-page site's pages are.

Sources

Checked 11 November 2026. The click paths above are the ones in Google's help pages, opened 11 November 2026. No step in this guide was run inside a live Analytics property or a live Tag Manager container, because both need a Google sign-in and this site does not sign in as anyone. The container file was validated structurally instead: export format version 2, every tag's firing trigger resolves, every variable the tags reference exists in the file, and the tag types are the documented ones. Google moves menu items without warning; if a path on this page no longer matches, the linked help page is the current map. The container fragment for this guide is 1124 bytes (1.10 KB, 0.55% of the reported 200 KB container size limit), MD5 dce4c5f5c99443af232b2812837e8a8c, and passed 12 structural checks.