GTM Browser Viewport Measurement

Browser Viewport Measurement in Google Tag Manager — A Complete Recipe Without Custom JavaScript

Send the user's current browser viewport dimensions as an event-level parameter on every GA4 hit — using only GTM's built-in JavaScript Variable type and a Constant. No Custom HTML tags. No new CSP hashes.

Published · Tags: GTM, Google Analytics 4, Browser Viewport, Window Size, JavaScript Variable, CSP, GA4, Shared Event Settings

How big is the browser window when a user converts? Does your checkout funnel drop off more on narrow viewports than wide ones? These questions sound like they should be answerable from GA4's built-in device category dimension — but device category is derived from the User-Agent string and tells you nothing about how much screen space the browser is actually using. A desktop user with a browser snapped to half-screen is classified as "desktop" even though their experience is closer to a narrow tablet. This recipe measures the actual viewport: the rendered area available to your page at the moment of each event.

Crucially, viewport size is an event-level metric, not a user or session property. A user can resize their browser, rotate their phone, or dock a floating panel at any point during a session. Capturing it at session start misses every mid-session change. Sending it as a shared event parameter on every GA4 hit means you always know the viewport at the exact moment each interaction happened.

⬇ Download the GTM Container JSON from Google Drive

Why Viewport Size Is an Event-Level Metric

It is tempting to record viewport dimensions once — at page load or session start — and treat them as a fixed property for the session. This is wrong for several reasons.

Users resize browser windows during long sessions. Mobile users rotate their devices between portrait and landscape. Tablet users attach and detach keyboards that alter the usable viewport. Users on multi-monitor setups drag the browser between screens with different resolutions. In all these cases, a single session-start capture would reflect the viewport at the wrong moment.

By attaching the viewport to every event via the Shared Event Settings variable, you get the actual viewport at the time of every page view, click, form submission, and conversion. This means you can ask GA4: "Of the users who completed checkout, what was their viewport size at the moment they clicked the buy button?" — not "what was it when they arrived?"

Why this matters for CSP: Measuring viewport dimensions might seem to require a Custom HTML tag or inline <script> block that reads window.innerWidth. That approach would require a new SHA-256 hash in your Content Security Policy every time the script content changed. This recipe avoids all of that: GTM's built-in JavaScript Variable type reads any JavaScript expression from the page context without introducing new CSP requirements. No custom JavaScript. No new CSP hashes.

What's Inside the Container

The exported container (browser-viewport-measurement-recipe.json) adds one new folder of assets to the existing Analytics infrastructure, and wires the result into the shared event settings so every GA4 hit carries the viewport dimension automatically.

Folders

FolderContains
Analytics GA4 configuration tag, shared settings variables, environment/stream routing variables, client ID utilities, and session/timestamp infrastructure — carried over from the base container.
Browser Viewport Six JavaScript Variable variables reading the three standard width sources and three height sources, plus the Browser Viewport Constant that assembles them into a single WIDTHxHEIGHT string.

Tags

TagTypePurpose
Google Analytics ConfigurationGoogle Tag (googtag)Standard GA4 config tag wired to environment-aware Stream IDs via shared configuration and event settings variables. Fires once per load. The shared event settings variable it references includes e_browser_viewport, so every hit from this container carries the viewport string.

Variables Added

VariableTypeJavaScript Expression
window.innerWidthJavaScript Variablewindow.innerWidth
window.innerHeightJavaScript Variablewindow.innerHeight
document.body.clientWidthJavaScript Variabledocument.body.clientWidth
document.body.clientHeightJavaScript Variabledocument.body.clientHeight
document.documentElement.clientWidthJavaScript Variabledocument.documentElement.clientWidth
document.documentElement.clientHeightJavaScript Variabledocument.documentElement.clientHeight
Browser ViewportConstant{{window.innerWidth}}x{{window.innerHeight}}

The Three Viewport Width Sources — and Why We Expose All Three

There is no single universally correct way to read browser viewport width in JavaScript. Three properties exist, each returning subtly different values depending on the browser engine, the presence of scrollbars, zoom level, and whether the page has a CSS viewport meta tag. This recipe exposes all three so you can compare them across your traffic.

window.innerWidth / window.innerHeight

The CSS viewport size — the space available to the page's layout after browser chrome (toolbar, address bar, tabs) is excluded. On most desktop browsers this includes the scrollbar width, so it may be slightly larger than the usable content area. This is the value most commonly recommended for responsive design breakpoint analysis because it matches what CSS @media queries see.

document.documentElement.clientWidth / clientHeight

The width of the <html> element's content box. Scrollbars are excluded, so this is typically a few pixels narrower than window.innerWidth on desktop when a vertical scrollbar is visible. On mobile, where scrollbars overlay the content rather than displacing it, the two values converge. This is often the most accurate proxy for "how wide is the content area the user actually sees."

document.body.clientWidth / clientHeight

The width of the <body> element's content box. This can differ from documentElement.clientWidth if the body has margins, padding, or an explicit width set in CSS. On pages with a centred fixed-width layout, body.clientWidth may be narrower than the viewport. Less reliable for breakpoint analysis, but useful as a diagnostic comparison when values diverge unexpectedly.

Which one to use in GA4 reports? The Browser Viewport Constant uses window.innerWidth and window.innerHeight because they most closely match the breakpoints your CSS framework responds to. The other four variables are available in GTM Preview for diagnostic comparison; you can promote any of them to a GA4 parameter if you have a specific reason to prefer a different source.

The Browser Viewport Constant: Assembling the String

The seven variables above are designed as building blocks. The Browser Viewport Constant ties them together into a single dimension value:

{{window.innerWidth}}x{{window.innerHeight}}

This produces strings like 1440x900, 390x844, or 1024x768 — a format familiar from screen resolution reporting and directly usable as a GA4 custom dimension value. The lowercase x separator is intentional: it avoids confusion with multiplication operators and is the conventional format used by analytics platforms and browser developer tools.

The Constant type was chosen deliberately over a more complex variable type. A Constant re-evaluates its template expression references at the time each event fires — it does not cache the value at page load. This means that if a user resizes their browser between a page view and a click event, the click event captures the resized viewport, not the original one. This is exactly the event-level behaviour described in the opening section.

How It Plugs Into Every GA4 Hit: Shared Event Settings

Rather than adding e_browser_viewport to every individual tag's event parameter table, the recipe wires {{Browser Viewport}} into the Google Tag Shared Event Settings variable. This variable is referenced by the Google Analytics Configuration tag, which means its parameter table is merged into every GA4 event sent by the container — page views, scroll events, click events, custom events, and conversions alike.

Shared Event Settings Parameters (full list)

GA4 ParameterGTM VariableDescription
container_id{{GTM Container ID}}The GTM container public ID (e.g. GTM-XXXXXX). Useful for filtering in BigQuery when multiple containers write to the same GA4 property.
environment_name{{GTM Environment Name}}The GTM Environment label (live, staging, development) when GTM Environments are in use.
stream_id{{Measurement Stream RegEx Lookup}}The GA4 Measurement ID that received this event, after environment routing. Confirms which stream a hit was routed to.
e_session_id{{Browser Session ID RegEx Table}}GA4 session ID extracted from the _ga_XXXXX cookie. Makes session-level joins in BigQuery straightforward.
e_client_id{{Browser Client ID Append Underscore}}GA4 client ID with trailing underscore to force BigQuery string typing and avoid float precision loss.
e_1st_session_ts{{Timestamp Session Start}}Unix timestamp of the start of the current session, from the GA4 session cookie.
e_cur_session_ts{{Timestamp Current}}Unix timestamp at the moment the event fired.
e_1st_user_ts{{Timestamp First User}}Unix timestamp of the user's first ever session, from the GA4 client ID cookie.
page_title{{Page Title}}The document.title value at event time.
e_browser_viewport{{Browser Viewport}}The viewport string — e.g. 1440x900 — captured at the moment this event fired.
Event-level, not session-level: Because e_browser_viewport is sent with every event via shared event settings, it reflects the viewport at event time, not at session start. A session where the user rotates their phone mid-way through will show the correct viewport for each individual event.

Why JavaScript Variable Type Does Not Require New CSP Hashes

Content Security Policy's script-src directive controls which JavaScript is permitted to execute. Custom HTML tags in GTM that contain <script> blocks with inline code must be covered by a SHA-256 hash of the script content (or an unsafe-inline allowance, which defeats CSP's purpose). Every time the script text changes — even by one character — the hash changes and must be updated in the CSP header.

GTM's JavaScript Variable type works differently. It evaluates a JavaScript expression inside GTM's own sandboxed runtime, which runs under the existing permission granted to the GTM container snippet. No new <script> tags are injected into the DOM. The expression window.innerWidth is read directly from the page's window object at variable evaluation time, inside code that is already covered by the GTM container's own CSP allowance.

The practical result: you can add, modify, or remove JavaScript Variable variables in GTM as many times as you like, and your CSP never needs updating. This is a significant operational advantage for organisations that maintain strict CSPs and require security team review for any hash change.

How to Import

  1. Download the JSON file from the Google Drive link.
  2. In GTM, go to Admin → Import Container.
  3. Upload browser-viewport-measurement-recipe.json.
  4. Choose Merge (not Overwrite) to preserve your existing tags and triggers.
  5. Update Measurement Stream ID Production and Measurement Stream ID Development constant variables to your actual GA4 Measurement IDs.
  6. Review the Measurement Stream RegEx Lookup variable and adjust hostname patterns to match your non-production environments.
  7. In GA4, register e_browser_viewport as a custom event-scoped dimension under Admin → Custom definitions → Custom dimensions.
  8. Open GTM Preview mode, interact with your site, and verify that the e_browser_viewport parameter appears on every event in the Tag Assistant panel before publishing.
Custom dimension registration: GA4 does not automatically expose new event parameters as dimensions in reports or Explorations. You must register e_browser_viewport as a custom event-scoped dimension in GA4 Admin before it becomes available for filtering, segmenting, or use in Funnel Explorations. Registration takes up to 24 hours to propagate into the GA4 interface, but the data is collected immediately once the container is published.

What You Can Do With It

  • Compare conversion rates across viewport size buckets — identify whether narrow viewports underperform wide ones on specific funnel steps, independent of device category
  • Detect mid-session viewport changes: a session where early events show 1440x900 and later events show 720x900 indicates a window resize, not a device switch
  • Validate responsive design breakpoints against real user data — see the actual distribution of viewport widths hitting each breakpoint boundary in your traffic
  • Correlate viewport size with scroll depth, time on page, or click-through rate to understand how layout affects engagement at different viewport widths
  • Use e_browser_viewport as a secondary dimension in GA4 Explorations to understand whether a high-exit page affects wide viewports, narrow viewports, or both equally
  • Filter BigQuery exports by e_browser_viewport to build custom viewport-aware cohort analyses without touching the raw User-Agent string

Attribution

The three-source approach to reading viewport dimensions — using window.innerWidth, document.documentElement.clientWidth, and document.body.clientWidth in parallel — is based on a solution by Matthew Edgar. The implementation here adapts that approach to GTM's built-in JavaScript Variable type and wires it into the Shared Event Settings pattern so it propagates automatically to all GA4 events without Custom HTML tags or CSP implications.


The full source — including the container JSON and variable inventory — is available on GitHub. If you adapt the viewport string format or add additional measurement sources, open a pull request or issue.

⬇ Download GTM Container JSON

Comments

Popular posts from this blog

Site Landing & Site Referrer Preservation

UTM & URL Query String 2 Cookies