The Chrome User Experience Report
What CrUX collects, from whom, over what window, and what it can and can't tell you. Twelve monthly releases of CrUX's public BigQuery dataset for three well-known sites and this course's own, read with SQL.
- 01Know whose page loads countSay which Chrome users, pages and sites CrUX counts, and which browsers it never hears from.
- 02Read a CrUX numberRead a p75 and a share of good page loads, for a whole site or one page, split by phone and desktop.
- 03Mind the windowExplain why a fix takes about four weeks to show, in the API's 28 days and in BigQuery's monthly releases.
Field data from Chrome's users
Every number in the last stage came from a test you ran. You loaded a page on your laptop, slowed it down to look like Asha's phone, and timed it. In Noise and variability, you loaded it again and again, and took the middle value. That was careful work. But every one of those loads was yours.
Now a different question. Take web.dev, a big site of articles about building websites. People read it on their own phones and laptops, over their own networks. How fast does it load for them? You can't stand behind each reader with a stopwatch. But their browsers can.
For people who agree to it, Chrome reports how their page loads went. Google adds those reports up, site by site, and publishes the result. That's the Chrome User Experience Report, or CrUX for short. Chrome's docs call it "a dataset that reflects how real-world Chrome users experience popular destinations on the web."
Here is that path for six of web.dev's readers:
Notice who is missing: people on other browsers, and Chrome users who didn't agree. CrUX never hears about their page loads.
Numbers measured during real visits are called field data. Numbers from a test you run yourself are lab data. You met both in Lab data vs field data. CrUX is field data, and anyone can read it.
A town wants to know how long the drive to work takes. A council worker could drive the route once, at ten in the morning, with a stopwatch. That's neat, and easy to repeat. But it's one trip, on a quiet road. So the town also runs a survey. Thousands of residents say how long their own trips took, in rain and in rush hour. The survey office adds up the answers and publishes a report.
- In real life: One test drive, timed by a council worker
- In CrUX: A lab test: one load of the page, on your laptop
- In real life: Thousands of residents' answers about their own trips
- In CrUX: Field data: page loads in real visitors' browsers
- In real life: The survey office adding up the answers
- In CrUX: Google adding up Chrome's reports
- In real life: The published survey report
- In CrUX: CrUX
Here is what CrUX's report says about web.dev for one month. It's about LCP, Largest Contentful Paint: the moment the biggest image or block of text appears. An LCP is good at 2,500 ms or less.
// web.dev's LCP in CrUX's September 2026 release.const shareGood = 0.7218;const p75 = 2600; console.log(`${Math.round(shareGood * 100)} in 100 page loads had a good LCP.`);console.log(`CrUX's 75th percentile: ${p75} ms.`);- Line 1 is a comment. CrUX publishes one release per month, and this is the one for September 2026.
- Line 2 is recorded from CrUX. It's the share of web.dev's page loads with a good LCP: 0.7218 means 72.18%.
- Line 3 is recorded too. It's CrUX's 75th percentile of all those LCPs, in milliseconds (ms). The next paragraph explains it.
- Line 5 prints
72 in 100 page loads had a good LCP. - Line 6 prints
CrUX's 75th percentile: 2600 ms.
What about the other 28 in 100? CrUX counts those too: 17 needed improvement, and 11 were poor, over 4,000 ms. One number can't hold all of that. So CrUX also gives the number in line 3.
The page load three quarters of the way along
Line 3's number is the one the Core Web Vitals are judged by. It's the 75th percentile, or p75. Picture every page load of the month standing in one line, the fastest LCP at the front and the slowest at the back. Walk three quarters of the way down the line, and stop. The LCP of the page load you stop at is the p75. Three in four page loads were that fast or faster.
The survey report doesn't print thousands of answers. Its headline is one sentence: "3 in 4 residents got to work within 20 minutes." The town's target is 25 minutes, so it meets it. The slowest quarter took longer. How much longer doesn't change the headline.
- In real life: Every resident's trip time, quickest to slowest
- In CrUX: Every page load's LCP, fastest to slowest
- In real life: 20 minutes: the answer three quarters of the way along
- In CrUX: The p75: 2,600 ms for web.dev
- In real life: The town's target: 25 minutes
- In CrUX: The good limit for LCP: 2,500 ms
- In real life: The slowest quarter of residents
- In CrUX: The slowest page loads, which the p75 doesn't describe
Why the 75th, and not the middle? Core Web Vitals gave the reasons. Most page loads should meet the target, and a few unlucky ones shouldn't decide the result.
Now put lines 2 and 3 together. Only 72 in 100 page loads were good, fewer than 75. So the page load three quarters of the way down the line wasn't good. That's why the p75, 2,600 ms, is over the 2,500 ms limit. It gives you a rule worth keeping: a good p75 means at least 3 in 4 page loads were good. By that rule, web.dev's LCP needed improvement that month.
Four questions behind one number
That one number leaves four questions open. Each gets its own section:
- Whose page loads? Not every reader's.
- Which pages? The whole site, or one page.
- On which devices? Phones and laptops get different numbers.
- Over which days? A fix made today shows up weeks later.
A last section covers what CrUX can't see at all.
CrUX is real Chrome users' page loads, added up for each site. Before you trust one of its numbers, ask whose page loads went in, which pages they were on, and which days they cover. Its headline number is the p75: the page load three quarters of the way down the line.
Why it matters: this is the field data Google's own tools show you. PageSpeed Insights and Search Console show CrUX's numbers, and you'll meet both in the next lesson. Google Search also uses CrUX as part of how it ranks pages.
Who is included and which pages qualify
Back to the 72 in 100. A hundred page loads of whose? Not every reader of web.dev. CrUX only counts some people, and only some sites.
The commute survey doesn't reach everyone. It only counts residents who signed up to take part, and some people never get the form at all. It also only prints a figure for a town that sent in enough answers. Five answers from a small village wouldn't make a fair figure, so the village gets none.
- In real life: Residents who signed up to take part
- In CrUX: Chrome users who turned on the right settings
- In real life: Residents who never got the form
- In CrUX: People on other browsers, or on Chrome for iPhone
- In real life: A town with enough answers to be fair
- In CrUX: A site or page with enough page loads
- In real life: A village with too few answers, and no figure
- In CrUX: A small site, with no numbers in CrUX
Which people count
CrUX's methodology page lists four things a Chrome user must do. Three are settings in Chrome, and one is the device:
- Turn on usage statistics reporting, a setting that lets Chrome send Google automatic reports, such as crashes and how much memory it uses.
- Sync their browsing history: Chrome keeps their history in their Google Account, so it's the same on all their devices.
- Not set a Sync passphrase. That's an extra password some people add, so Google can store their synced data without being able to read it.
- Use a supported platform: Chrome on a Windows, macOS, ChromeOS or Linux computer, or Chrome on Android.
Android counts in two more ways. The docs include "mobile apps using Custom Tabs and WebAPKs." A Custom Tab is a Chrome tab that an Android app opens inside itself. A WebAPK is a web app that someone added to their Android home screen from Chrome.
Here are the four rules as code, checked for Asha. She reads web.dev in Chrome on her Android phone:
// Asha's Chrome, checked against the four things CrUX asks of a user.const asha = { usageStats: true, historySync: true, syncPassphrase: false, platform: "Android" };const supported = ["Windows", "macOS", "ChromeOS", "Linux", "Android"]; const counted = asha.usageStats && asha.historySync && !asha.syncPassphrase && supported.includes(asha.platform); console.log(counted ? "Asha's page loads count." : "Asha's page loads don't count.");- Line 2 describes Asha's Chrome: both settings on, no Sync passphrase, on Android.
- Line 3 lists the supported platforms.
- Lines 5 to 9 set
countedtotrueonly if all four rules hold. Each of lines 6 to 9 is one rule, and&&between them means "and". - On line 8, the
!means "not": she must not have a Sync passphrase. - Line 11 prints
Asha's page loads count.
Why it matters: CrUX only hears from people who turned on both settings, and nobody knows how many that is. The docs say so: "Chrome does not publish data about the proportions of users that meet these criteria." So you can't tell what share of your own readers CrUX speaks for.
Which sites and pages count
The second filter is on the page. A page has to pass two tests, and so does a whole site:
- Publicly discoverable. A search engine would be allowed to list it. CrUX uses "the same indexability criteria as search engines."
- Sufficiently popular. It has enough visitors. "An exact number is not disclosed," the docs say. It's chosen so that CrUX has "enough samples to be confident in the statistical distributions".
Two things make a page not discoverable. One is a status code other than 200, the code for "OK", after any redirects. The other is noindex, in a header or a <meta> tag. It's a note that tells search engines not to list the page.
A site that fails the popularity test is the village with too few answers. The bar is the same for a page and for a whole site. And you can't ask to be let in: "you cannot manually submit pages or origins for inclusion." A site joins on its own once it has enough page loads, and leaves again if it drops below.
So how many sites made it? CrUX's monthly releases are stored in BigQuery, Google's database for very large tables. You can query them with SQL from a Google Cloud account. The course counted the sites in the September 2026 release:
SELECT COUNT(*) AS originsFROM `chrome-ux-report.materialized.metrics_summary`WHERE date = "2026-09-01"- Line 1 counts rows.
COUNT(*)is SQL for "how many rows". - Line 2 names the table: CrUX's summary table, with one row per site and month.
- Line 3 keeps only the September 2026 release.
- A free dry run first estimated that the query would read 147 MB. Then it ran, and returned
18376044.
That's 18,376,044 sites in one month. CrUX calls each one an origin, and the next section explains why.
CrUX also says roughly how popular each site is, by its total number of page loads. It doesn't give a place in line. It gives a bucket: "top 1k, top 5k, top 10k, top 50k, top 100k, top 500k, top 1M, etc." In September 2026, en.wikipedia.org was in the top 1,000 bucket, and web.dev in the top 500,000. This course's own site had just joined CrUX, in the 50,000,000 bucket: the one after the top 10 million.
Origins vs URLs
The 72 in 100 was for all of web.dev. But web.dev has many articles. Is the number for the whole site, or for one page? CrUX keeps both:
- An origin is a whole site: the scheme, the host and the port together, like
https://web.dev. - A URL, here, is one page's address, like
https://web.dev/articles/vitals.
Think of the survey again. It prints a figure for the whole town. It prints one for a single street only if enough people on that street answered.
An origin gathers every page
An origin's numbers hold page loads from all its pages. That includes pages that don't qualify on their own. In the docs' words, they're added up "regardless of individual page discoverability."
A single page has to pass both tests by itself: discoverable, and popular enough. So a busy origin can have quiet pages with no numbers of their own. Why it matters: a page with no data of its own isn't missing from CrUX. Its page loads are inside the origin's numbers.
One page, many addresses
People reach the same page by slightly different addresses. A link in an email may add ?utm_medium=email, a query string. A link to a heading may add #lcp, a fragment. CrUX removes both, "so that all user experiences on the page are aggregated together."
// Three addresses for the same web.dev page.const addresses = [ "https://web.dev/articles/vitals?utm_medium=email", "https://web.dev/articles/vitals#lcp", "https://web.dev/articles/vitals",]; const pageOf = (address) => address.split(/[?#]/)[0];const pages = new Set(addresses.map(pageOf)); console.log(`${addresses.length} addresses, ${pages.size} page: ${[...pages].join(", ")}`);- Lines 2 to 6 list three addresses for the same article.
- Line 8 cuts an address at the first
?or#, and keeps the part before it. - Line 9 cuts all three. A
Setkeeps each different value only once. - Line 11 prints
3 addresses, 1 page: https://web.dev/articles/vitals.
That helps a page count. Three small piles of page loads become one pile, big enough to pass the popularity test. The docs warn about one rare case. If ?productID=101 and ?productID=102 are two different products, CrUX still counts them as one page.
Frames and single-page apps
Two more rules follow what the visitor sees:
- Iframes. An iframe is a page shown inside another page. It isn't reported on its own. Its page loads "do contribute to the metrics of the top-level page": the page in the address bar. So if the iframe's content jumps around, the outer page's layout shift score gets worse.
- Single-page apps. Some sites change pages with JavaScript instead of loading a new one. To the visitor, each change looks like a new page. To Chrome, it all belongs to the first page. In the docs' words, "the entire experience is attributed to the initial page view."
Page-level numbers are in PageSpeed Insights and the CrUX API. BigQuery only has origins, one row per origin and month. That's why every number in this lesson is for a whole site.
Form factors and connection types
Back to web.dev's 2,600 ms. Its readers use phones, tablets and laptops. Did they all wait the same? A good commute survey splits its answers by bus and by car, because the trips are different. CrUX splits its page loads in a similar way.
Phone, tablet or desktop
Each kind of device is a form factor, and CrUX knows three: phone, tablet and desktop. A way of splitting the page loads like this is called a dimension.
How does CrUX know which is which? "Form factor is inferred from the device User-Agent string." The User-Agent string is a line of text the browser sends with every request. It names the browser, and the device it runs on.
Different sites have very different readers. Here is each site's split, for the same month:
// The share of each origin's page loads by form factor, in CrUX's September 2026 release.const shares = { "web.dev": { phone: 0.4209, desktop: 0.567, tablet: 0.0119 }, "developer.mozilla.org": { phone: 0.2236, desktop: 0.7579, tablet: 0.018 }, "en.wikipedia.org": { phone: 0.7959, desktop: 0.1846, tablet: 0.019 },}; for (const [origin, share] of Object.entries(shares)) { console.log(`${origin}: ${Math.round(share.phone * 100)}% of page loads on phones`);}- Line 1 is a comment.
- Lines 3 to 5 are recorded from CrUX: each site's share of page loads on each form factor. 0.4209 means 42.09%.
- Line 9 turns the phone share into a whole percentage and prints it, once for each site.
- It prints
web.dev: 42% of page loads on phones,developer.mozilla.org: 22% of page loads on phonesanden.wikipedia.org: 80% of page loads on phones.
Most of Wikipedia's page loads are on phones. Most of MDN's are on desktops. web.dev sits in between. Why it matters: a site's overall number leans toward its biggest group. Wikipedia's overall number is mostly a phone number.
So web.dev's own article on Web Vitals says to split them: "a good threshold to measure is the 75th percentile of page loads, segmented across mobile and desktop devices." Here is web.dev's LCP, split:
// web.dev's p75 LCP in CrUX's September 2026 release: all page loads, then each form factor, in ms.const lcp = { all: 2600, phone: 3000, tablet: 2800, desktop: 2300 }; for (const [device, value] of Object.entries(lcp)) { const rating = value <= 2500 ? "good" : value <= 4000 ? "needs improvement" : "poor"; console.log(`${device}: ${value} ms, ${rating}`);}- Line 2 is recorded: web.dev's p75 LCP for all page loads, then for each form factor.
- Line 5 rates each one: good up to 2,500 ms, poor over 4,000 ms, and needs improvement in between.
- It prints
all: 2600 ms, needs improvementandphone: 3000 ms, needs improvement. - Then
tablet: 2800 ms, needs improvementanddesktop: 2300 ms, good.
One site, two answers. Desktop readers got a good LCP. Phone readers waited 700 ms longer at the p75, and needed improvement. The overall 2,600 ms hid both. Why it matters: to pass, web.dev would have to work on phones and tablets. Making desktops faster wouldn't move the phone number at all.
How fast their networks are
For networks, CrUX changed in 2025. It used to sort page loads by labels like "4g" and "3g", called effective connection types. The docs: "The Effective Connection Type dimension was removed from February 2025 and replaced with the RTT metric."
RTT is the round trip time: how long a message takes to reach a server and come back. Choosing test conditions used it to pick a network for the lab. Notice whose number it is. It's "a measure of the users to your site (based on their recent internet activity), and not of the site itself." It describes your readers' networks, not your server.
In September 2026, the p75 round trip was 150 ms for web.dev, 125 ms for developer.mozilla.org and 100 ms for en.wikipedia.org. The number comes from a property any page can read in Chrome, navigator.connection.rtt: the browser's own estimate of the round trip. Firefox and Safari don't have it.
CrUX has one more dimension: the reader's country, worked out from their IP address. It's only in BigQuery: "Country dimensions are not available in the CrUX APIs."
The 28-day window
Say web.dev's team fixes its slow phone LCP today. When will CrUX show it? Not tomorrow. The reason is the window: the days of page loads that each number covers.
A weather station's board shows the average temperature of the last 28 days. Today is suddenly hot, but the board barely moves: today is one day in 28. If the heat lasts, the board climbs a little every morning. After four weeks, every day on the board is a hot one. At the end of each month, the station also prints that month's figure in a report.
- In real life: Each day's temperature
- In CrUX: Each day's page loads
- In real life: The board: the last 28 days together
- In CrUX: The CrUX API's numbers
- In real life: One hot day barely moving the board
- In CrUX: A fix barely moving CrUX the next day
- In real life: The figure printed at the end of each month
- In CrUX: BigQuery's monthly release
The CrUX API: the last 28 days
The CrUX API is how programs ask CrUX for numbers. Its window moves. The docs: "The data in the Chrome UX Report is a 28-day rolling average of aggregated metrics." In plain words, every answer covers the past 28 days, added up together.
The window moves forward one day at a time: "Data is updated daily around 04:00 UTC." It also runs about two days behind today, because it waits until each day's data is complete. So after a fix, the window fills with new days slowly:
A few days after the fix, almost all of the window's 28 days are still from before it. Two weeks later, it's half and half. Only after four weeks is every day from after the fix. Why it matters: to check a fix in CrUX, wait about four weeks. To see it sooner, measure your own visitors, as Lab data vs field data suggested.
BigQuery: one release per month
BigQuery keeps a different window. It has one table per month, and each holds "the last 28 days of each month period". That's the weather station's printed monthly figure. A month's table comes out on "the second Tuesday of the following month."
Each row of this table asks one question of both:
| Question | The CrUX API | BigQuery |
|---|---|---|
| Which days does a number cover? | The last 28 days. The window moves forward every day. | The last 28 days of one month. There's one table per month. |
| When do new numbers come out? | Every day, around 04:00 UTC. | On the second Tuesday of the next month. |
| One page, or a whole site? | Either: one page, or a whole origin. | Whole origins only. |
| Split by country? | No. | Yes, in tables of their own. |
| How exact is the p75? | It's worked out from all the data. | It's coarse. Every LCP in this lesson's tables is a multiple of 100 ms. |
Month to month, near the limit
Monthly releases make a site's history easy to read. Here is web.dev's p75 LCP across twelve of them:
// web.dev's p75 LCP in CrUX's monthly releases, October 2025 to September 2026, in ms.const lcp = [2400, 2600, 2500, 2600, 2500, 2400, 2400, 2400, 2500, 2600, 2600, 2600];const good = lcp.filter((value) => value <= 2500).length; console.log(`good in ${good} of ${lcp.length} months`);console.log(`from ${Math.min(...lcp)} to ${Math.max(...lcp)} ms`);- Line 2 is recorded: web.dev's p75 LCP in each monthly release, oldest first.
- Line 3 counts the months at 2,500 ms or less: the good ones.
- It prints
good in 7 of 12 monthsandfrom 2400 to 2600 ms.
web.dev's LCP never moved more than 200 ms. Yet its rating flipped between good and needs improvement five times. Nothing dramatic happened: it just sits right on the limit. Why it matters: near a limit, one month's rating says little. Read several months together.
Pick a site and a metric to see its twelve releases for yourself:
Found in all 12 monthly releases. In September 2026, its p75 LCP was 2,600 ms: needs improvement. Popularity: the top 500,000 bucket. 42% of its page loads were on phones.
A p75 in steps of 100 ms
Look at line 2 again: every value ends in 00. BigQuery's summary tables give a rough p75, which its docs call "the coarse 75th percentile". CrUX's tools page explains why. In BigQuery, "percentiles are interpreted from coarse histogram data which results in approximate values." A histogram, here, is a count of page loads in ranges of LCP.
Near the limit, that roughness matters. Here is December 2025:
// web.dev's LCP in CrUX's December 2025 release, from BigQuery's summary table.const shareGood = 0.7446;const p75 = 2500; const share = shareGood >= 0.75 ? "at least 3 in 4" : "fewer than 3 in 4";const rating = p75 <= 2500 ? "good" : "not good";console.log(`Good page loads: ${(shareGood * 100).toFixed(2)}%, ${share}.`);console.log(`The table's p75: ${p75} ms, rated ${rating}.`);- Line 2 is recorded: the share of web.dev's page loads with a good LCP that month.
- Line 3 is recorded too: the summary table's p75 for the same month.
- Line 5 checks the share against 3 in 4. Line 6 rates the p75.
- Line 7 prints
Good page loads: 74.46%, fewer than 3 in 4. - Line 8 prints
The table's p75: 2500 ms, rated good.
The two lines disagree. By the rule from the start of the lesson, fewer than 3 in 4 good page loads means the p75 isn't good. So the true p75 was a little over 2,500 ms, and the table printed 2,500. Why it matters: when a p75 sits right on a limit, check the share of good page loads too. The share has four decimal places; the p75 comes in steps.
What CrUX can't see
Remember the residents who never got the survey form? CrUX has them too. Some page loads never reach it, no matter how many of them there are.
Browsers it never hears from
CrUX comes from Chrome, so people on Safari and Firefox are never in it. Its docs also list "a few notable exceptions that don't provide data to the CrUX dataset":
- Chrome on iOS: Chrome on an iPhone or iPad.
- Android apps using WebView: apps that show web pages inside themselves, without opening a browser.
- Other Chromium browsers, such as Microsoft Edge. Chromium is the open-source project Chrome is built from.
Those browsers can measure the same things. Safari and Firefox now report LCP, for example. They just don't send it to CrUX:
Why it matters: if many of your readers use iPhones, CrUX can't speak for them. You need field data of your own from every browser, the subject of Field data from every browser.
Small sites, missing metrics
Small sites get less still. This course's own site, completefrontend.com, appeared in just 1 of the twelve releases: the last one, September 2026. Even then, some numbers were missing:
// completefrontend.com in CrUX's September 2026 release: each 75th percentile, null where CrUX gave none.const p75 = { lcp: 1000, inp: null, cls: 0, fcp: 900, ttfb: null, rtt: null };const metrics = Object.keys(p75); console.log(`reported: ${metrics.filter((metric) => p75[metric] !== null).join(", ")}`);console.log(`missing: ${metrics.filter((metric) => p75[metric] === null).join(", ")}`);- Line 2 is recorded: completefrontend.com's p75 for each metric in its first release.
nullmeans CrUX gave no value. - Line 3 lists the metrics' names.
- Line 5 keeps the ones with a value. Line 6 keeps the ones without.
- It prints
reported: lcp, cls, fcpandmissing: inp, ttfb, rtt.
INP is the easiest gap to explain. It measures how fast a page answers a click, a tap or a key press. web.dev's INP article lists reasons a page can have no INP, starting with this one: "the user never clicked, tapped, or pressed a key on their keyboard." Scrolling isn't measured either. So a reader who only scrolls and reads gives CrUX no INP at all.
Why it matters: a missing metric isn't a zero, and it isn't a pass. It means CrUX has no number for you, and you need your own.
Noise on purpose, and no causes
CrUX also changes its numbers a little, on purpose: "A small amount of randomness is applied to the dataset to prevent reverse-engineering of sensitive data, such as total traffic volumes." In plain words, it adds a little random noise, so nobody can work out how many visitors a site has. The docs add that this "does not affect the accuracy of aggregate statistics."
The biggest blind spot is the cause. CrUX says what readers got, not why. It has no traces, no element names and no scripts. It can tell web.dev that phones wait 3,000 ms for their LCP. It can't name the image, or the script that held it up. As Lab data vs field data put it: the field tells you whether; the lab tells you why. To see causes in the field, you measure your own visitors. That's the rest of Measuring real users.
In CrUX or not?
Each card is one page load. Sort it by whether CrUX can count it, using the rules from "Who is included". Unless the card says otherwise, the person has both settings on, and the site is popular and public.
- A page load in Chrome on an Android phone
- A page load on a
noindexpage of a popular, public site - A page an Android app opens in a Custom Tab
- A page load of an address ending in
?utm_medium=email - A page load in Chrome on an iPhone
- A page load in Microsoft Edge
- A page load in Chrome on Windows, by someone with a Sync passphrase
- A page shown in an Android app's WebView
Sort each page load by whether CrUX can count it.
Common mix-ups
- "CrUX covers every visitor." No. It only has Chrome users on desktop and Android who share usage statistics and sync their history.
- "A p75 of 2,600 ms means a typical page load took 2,600 ms." No. It's the page load three quarters of the way down the line. Three in four were that fast or faster.
- "My fix will show in CrUX tomorrow." No. Tomorrow, the API's window still holds almost four weeks from before the fix. BigQuery waits for the next monthly release.
- "One overall number is enough." No. web.dev's desktops were good at 2,300 ms, while its phones needed improvement at 3,000 ms.
- "A page with no data of its own isn't in CrUX." No. Its page loads still count toward its origin.
- "Every site in CrUX has every metric." No. The course's site had no INP, TTFB or round trip time in its first release.
- "BigQuery's p75 is exact." No. It comes in steps. In December 2025, it said 2,500 ms, good, though only 74.46% of web.dev's page loads were good.
Practice exercises
Answer first, then use the hints.
Asha switches to an iPhone, and keeps reading web.dev in Chrome. What does line 11 print now?
// Asha's Chrome, checked against the four things CrUX asks of a user.
const asha = { usageStats: true, historySync: true, syncPassphrase: false, platform: "iOS" };
const supported = ["Windows", "macOS", "ChromeOS", "Linux", "Android"];
const counted =
asha.usageStats &&
asha.historySync &&
!asha.syncPassphrase &&
supported.includes(asha.platform);
console.log(counted ? "Asha's page loads count." : "Asha's page loads don't count.");Asha's page loads don't count. iOS isn't a supported platform, so line 9 is false, and so is the whole check.
If web.dev's phones had a p75 LCP of 2,400 ms, what would the second line print?
// web.dev's p75 LCP in CrUX's September 2026 release: all page loads, then each form factor, in ms.
const lcp = { all: 2600, phone: 2400, tablet: 2800, desktop: 2300 };
for (const [device, value] of Object.entries(lcp)) {
const rating = value <= 2500 ? "good" : value <= 4000 ? "needs improvement" : "poor";
console.log(`${device}: ${value} ms, ${rating}`);
}phone: 2400 ms, good. Only the phone line changes. The all value is a separate recorded number, so it stays 2,600 in this code.
If web.dev's September 2026 p75 had been 2,500 ms, what would the first line print?
// web.dev's p75 LCP in CrUX's monthly releases, October 2025 to September 2026, in ms.
const lcp = [2400, 2600, 2500, 2600, 2500, 2400, 2400, 2400, 2500, 2600, 2600, 2500];
const good = lcp.filter((value) => value <= 2500).length;
console.log(`good in ${good} of ${lcp.length} months`);
console.log(`from ${Math.min(...lcp)} to ${Math.max(...lcp)} ms`);good in 8 of 12 months. 2,500 ms is right on the limit, and a value on the limit is good.
In the playground, what was en.wikipedia.org's p75 INP in September 2026, in ms?
75 ms: good, well under the 200 ms limit.
You query BigQuery with WHERE origin = "https://Web.dev" and get no rows. CrUX's methodology page says how every origin in the dataset is written. How?
In lowercase. The docs: "all origins in the CrUX dataset are lowercase." So write https://web.dev.
Every answer from the CrUX API says which days its window covers. What's the name of the field that holds them?
collectionPeriod. Its firstDate and lastDate say which days the window covers, so you can tell whether today's update has arrived.
Check your understanding
For each question, ask whose page loads count, which page or origin they count toward, and which days they cover.
Question 1 of 7What does the first line of the twelve monthly releases example print?
Read the code, then predict// web.dev's p75 LCP in CrUX's monthly releases, October 2025 to September 2026, in ms. const lcp = [2400, 2600, 2500, 2600, 2500, 2400, 2400, 2400, 2500, 2600, 2600, 2600]; const good = lcp.filter((value) => value <= 2500).length; console.log(`good in ${good} of ${lcp.length} months`); console.log(`from ${Math.min(...lcp)} to ${Math.max(...lcp)} ms`);Choose an answer to see the explanation.
Question 2 of 7What does the second line of the course site example print?
Read the code, then predict// completefrontend.com in CrUX's September 2026 release: each 75th percentile, null where CrUX gave none. const p75 = { lcp: 1000, inp: null, cls: 0, fcp: 900, ttfb: null, rtt: null }; const metrics = Object.keys(p75); console.log(`reported: ${metrics.filter((metric) => p75[metric] !== null).join(", ")}`); console.log(`missing: ${metrics.filter((metric) => p75[metric] === null).join(", ")}`);Choose an answer to see the explanation.
Question 3 of 7Only 72 in 100 of a site's page loads had a good LCP. Is its p75 LCP good?
Choose an answer to see the explanation.
Question 4 of 7Which of these page loads can never reach CrUX?
Choose an answer to see the explanation.
Question 5 of 7Your origin is in CrUX, but one of its pages has no page-level data. Why might that be?
Choose an answer to see the explanation.
Question 6 of 7You shipped a fix today. When will the CrUX API's numbers show it fully?
Choose an answer to see the explanation.
Question 7 of 7web.dev's p75 LCP was 2,600 ms overall, 3,000 ms on phones and 2,300 ms on desktops. What should you report?
Choose an answer to see the explanation.
Key takeaways
- CrUX is field data: real Chrome users' page loads, added up by Google for each site and popular page.
- It only counts Chrome on desktop and Android, from people who share usage statistics and sync their history.
- Its headline number is the p75. A good p75 means at least 3 in 4 page loads were good.
- An origin gathers all its pages' page loads. A single page needs enough page loads of its own.
- Split by form factor: one site can be good on desktops and need improvement on phones.
- The API covers the last 28 days, and BigQuery one month at a time. A fix takes about four weeks to show fully.
- It never sees Safari, Firefox or Chrome on iOS, and never says why. Measure your own visitors for that.
Remember the one-liner.
CrUX is a survey of real Chrome users: know whose page loads, which pages, and which days.
Sources
The facts on this page come from these docs. The numbers come from the Chrome UX Report's BigQuery dataset, by Google, licensed CC BY 4.0.
- Overview of CrUX, Chrome UX Report docs.
- Methodology: eligibility, data quality and license, Chrome UX Report docs.
- Get your bookmarks, passwords, and more on all your devices: sync and passphrases, Google Chrome Help.
- Overview of Android Custom Tabs, Chrome for Developers.
- WebAPKs on Android, web.dev.
- Metrics: popularity and round trip time, Chrome UX Report docs.
- Dimensions, Chrome UX Report docs.
- Web Vitals, web.dev.
- CrUX API, Chrome UX Report docs.
- CrUX tools: windows, levels and dimensions, Chrome UX Report docs.
- CrUX on BigQuery, Chrome UX Report docs.
- Interaction to Next Paint (INP): what if no INP value is reported?, web.dev.
- Start or stop automatically reporting errors & crashes, Google Chrome Help.
- Run a query: dry runs, Google Cloud, BigQuery.
- Chromium, The Chromium Projects.
Coming next: PageSpeed Insights and Search Console, also in Measuring real users. You'll read CrUX's numbers next to a Lighthouse test on one screen, and use Search Console to find groups of slow pages.