How-to

How to Track Core Web Vitals (LCP, CLS, INP) with GTM

How to Track Core Web Vitals (LCP, CLS, INP) with GTMfield data from real usersLCP2.1sCLS0.06INP240ms

Core Web Vitals are how Google measures real-world page experience, and your lab scores in PageSpeed Insights often don't match what actual users feel. Sending Web Vitals to GA4 via GTM gives you field data, segmented by page, device and browser, so you can fix what really hurts. Here's how to set it up.

The three metrics

  • LCP (Largest Contentful Paint), loading: when the main content appears. Good < 2.5s.
  • CLS (Cumulative Layout Shift), stability: how much the page jumps around. Good < 0.1.
  • INP (Interaction to Next Paint), responsiveness: lag after a user interacts. Good < 200ms. (INP replaced FID.)

Why field data beats lab scores

A PageSpeed score is a single lab run on one simulated device. Your users are on real phones, real networks, real conditions. Field data, collected from actual visitors, tells you where experience is genuinely poor, and for whom. That's what you want driving fixes.

How to capture them with GTM

The reliable approach is Google's web-vitals library, which reads the metrics from the browser's PerformanceObserver and reports each one. You push each metric to the dataLayer, then send it to GA4.

// In a Custom HTML tag (or your app), using the web-vitals library
import {onLCP, onCLS, onINP} from 'web-vitals';

function send({name, value, id}) {
  window.dataLayer.push({
    event: 'web_vitals',
    metric_name: name,        // LCP | CLS | INP
    metric_value: Math.round(name === 'CLS' ? value * 1000 : value),
    metric_id: id
  });
}
onLCP(send); onCLS(send); onINP(send);
  1. Create a Custom Event trigger for web_vitals.
  2. Add Data Layer Variables for metric_name, metric_value, metric_id.
  3. Fire a GA4 Event tag (event name web_vitals) with those as parameters.

One event, three metrics

Send a single web_vitals event with a metric_name parameter rather than three separate events, it's cleaner to report on and keeps your event count down.

Practice this on a real container

Pushing performance metrics via PerformanceObserver and tagging them is a great hands-on rep. Practice capturing LCP, CLS and INP into the dataLayer and sending them to GA4.

Practice Web Vitals tracking →

Reporting on it

  • In GA4, build an exploration on the web_vitals event, breaking down by metric_name.
  • Segment by page, device category and browser to find the worst offenders.
  • Look at the distribution, not just the average, a bad 75th percentile is what Google scores you on.

Gotchas

  • CLS and INP finalize late. They're reported on page hide/unload, use sendBeacon-friendly sending so the hit survives.
  • Don't sample too hard. You need enough volume per page to trust the 75th percentile.
  • Round sensibly. Store CLS scaled (e.g. ×1000) so it's a clean integer parameter.

Now go practice it

Reading sticks when you do it. These hands-on lessons load your own GTM container and let you debug in Tag Assistant.

Frequently asked questions

How do I track Core Web Vitals in GA4 with GTM?

Use Google's web-vitals library to read LCP, CLS and INP from the browser's PerformanceObserver, push each to the dataLayer as a web_vitals event, then in GTM add a Custom Event trigger and a GA4 Event tag that sends metric_name, metric_value and metric_id as parameters. Report on it in a GA4 exploration broken down by metric_name.

What are LCP, CLS and INP?

LCP (Largest Contentful Paint) measures loading, when the main content appears (good under 2.5s). CLS (Cumulative Layout Shift) measures visual stability, how much the page jumps (good under 0.1). INP (Interaction to Next Paint) measures responsiveness, lag after a user interacts (good under 200ms). INP replaced FID as a Core Web Vital.

Why track Web Vitals instead of using PageSpeed Insights?

PageSpeed lab scores are a single simulated run on one device, which often doesn't match what real users experience. Field data collected from your actual visitors, segmented by page, device and browser, tells you where experience is genuinely poor, and Google scores you on the field 75th percentile, not the lab number.

Should I send each Web Vital as a separate event?

No, send a single web_vitals event with a metric_name parameter (LCP, CLS or INP) rather than three separate events. It's cleaner to report on and keeps your GA4 event count down. Note that CLS and INP finalize late, so send in a way that survives page unload.

Related posts

About the author

Nathan Gage
Nathan Gage

Analytics & Tag Management Consultant

Nathan Gage got his start in marketing through Google Tag Manager. Seeing how tracking customer behavior could turn raw clicks into insight you can actually act on is what pulled him into the field. Since then he has worked both full time and as a consultant with 15 marketing agencies, supporting brands that spend anywhere from a thousand dollars a month to over a million. Along the way he built a multi-touch attribution app, and he created The Happy Tagger so anyone can practice GTM, GA4 and server-side tracking on a real container instead of a production site.