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.
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 DriveWhy 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?"
<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
| Folder | Contains |
|---|---|
| Analytics | GA4 configuration tag, shared settings variables, environment/stream routing variables, client ID utilities, and session/timestamp infrastructure — carried over from the base container. |
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
| Tag | Type | Purpose |
|---|---|---|
Google Analytics Configuration | Google 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
| Variable | Type | JavaScript Expression |
|---|---|---|
window.innerWidth | JavaScript Variable | window.innerWidth |
window.innerHeight | JavaScript Variable | window.innerHeight |
document.body.clientWidth | JavaScript Variable | document.body.clientWidth |
document.body.clientHeight | JavaScript Variable | document.body.clientHeight |
document.documentElement.clientWidth | JavaScript Variable | document.documentElement.clientWidth |
document.documentElement.clientHeight | JavaScript Variable | document.documentElement.clientHeight |
Browser Viewport | Constant | {{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.
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 Parameter | GTM Variable | Description |
|---|---|---|
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. |
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
- Download the JSON file from the Google Drive link.
- In GTM, go to Admin → Import Container.
- Upload
browser-viewport-measurement-recipe.json. - Choose Merge (not Overwrite) to preserve your existing tags and triggers.
- Update
Measurement Stream ID ProductionandMeasurement Stream ID Developmentconstant variables to your actual GA4 Measurement IDs. - Review the
Measurement Stream RegEx Lookupvariable and adjust hostname patterns to match your non-production environments. - In GA4, register
e_browser_viewportas a custom event-scoped dimension under Admin → Custom definitions → Custom dimensions. - Open GTM Preview mode, interact with your site, and verify that the
e_browser_viewportparameter appears on every event in the Tag Assistant panel before publishing.
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
1440x900and later events show720x900indicates 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_viewportas 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_viewportto 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
Post a Comment