Learn how to use the Chrome UX Report API to get access to real-user experience data across millions of websites.
The Chrome UX Report (CrUX) dataset represents how real-world Chrome users experience popular destinations on the web. Since 2017, when the queryable dataset was first released on BigQuery, field data from CrUX has been integrated into developer tools like PageSpeed Insights, the CrUX Dashboard, and Search Console's Core Web Vitals report, enabling developers to measure and monitor real-user experiences. The piece that has been missing all this time has been a tool that provides free and RESTful access to CrUX data programmatically. To help bridge that gap, we're excited to announce the release of the all new Chrome UX Report API!
This API has been built with the goal of providing developers with simple, fast, and comprehensive access to CrUX data. The CrUX API only reports field user experience data, unlike the existing PageSpeed Insights API, which also reports lab data from the Lighthouse performance audits. The CrUX API is streamlined and can quickly serve user experience data, making it ideally suited for real-time auditing applications.
To ensure that developers have access to all of the metrics that matter the most—the Core Web Vitals—the CrUX API audits and monitors Largest Contentful Paint (LCP), First Input Delay (FID), and Cumulative Layout Shift (CLS) at both the origin and URL level.
So let's dive in and see how to use it!
Query origin data
Origins in the CrUX dataset encompass all underlying page-level experiences. The following example demonstrates how to query the CrUX API for an origin's user experience data using cURL on the command line.
API_KEY="[YOUR_API_KEY]"
curl "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$API_KEY" \
--header 'Content-Type: application/json' \
--data '{"origin": "https://web.dev"}'
The curl
command is made up of three parts:
- The URL endpoint of the API, including the caller's private API key.
- The
Content-Type: application/json
header, indicating that the request body contains JSON. - The JSON-encoded request body, specifying the
https://web.dev
origin.
To do the same thing in JavaScript, use the CrUXApiUtil
utility,
which makes the API call and returns the decoded response (see also our Github variant
for more features including history and batch support).
const CrUXApiUtil = {};
// Get your CrUX API key at https://goo.gle/crux-api-key.
CrUXApiUtil.API_KEY = '[YOUR_API_KEY]';
CrUXApiUtil.API_ENDPOINT = `https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=${CrUXApiUtil.API_KEY}`;
CrUXApiUtil.query = function (requestBody) {
if (CrUXApiUtil.API_KEY == '[YOUR_API_KEY]') {
throw 'Replace "YOUR_API_KEY" with your private CrUX API key. Get a key at https://goo.gle/crux-api-key.';
}
return fetch(CrUXApiUtil.API_ENDPOINT, {
method: 'POST',
body: JSON.stringify(requestBody)
}).then(response => response.json()).then(response => {
if (response.error) {
return Promise.reject(response);
}
return response;
});
};
Replace [YOUR_API_KEY]
with your key. Next, call the CrUXApiUtil.query
function and pass in the request body object.
CrUXApiUtil.query({
origin: 'https://web.dev'
}).then(response => {
console.log(response);
}).catch(response => {
console.error(response);
});
If data exists for this origin, the API response is a JSON-encoded object containing metrics representing the distribution of user experiences. The distribution metrics are histogram bins and percentiles.
{
"record": {
"key": {
"origin": "https://web.dev"
},
"metrics": {
"largest_contentful_paint": {
"histogram": [
{
"start": 0,
"end": 2500,
"density": 0.7925068547983514
},
{
"start": 2500,
"end": 4000,
"density": 0.1317422195536863
},
{
"start": 4000,
"density": 0.07575092564795324
}
],
"percentiles": {
"p75": 2216
}
},
// ...
}
}
}
The start
and end
properties of the histogram
object represent the range of values users experience for the given metric. The density
property represents the proportion of user experiences within that range. In this example, 79% of LCP user experiences across all web.dev pages are under 2,500 milliseconds, which is the "good" LCP threshold. The percentiles.p75
value means that 75% of user experiences in this distribution are less than 2,216 milliseconds. Learn more about the response structure in the response body documentation.
Errors
When the CrUX API doesn't have any data for a given origin, it responds with a JSON-encoded error message:
{
"error": {
"code": 404,
"message": "chrome ux report data not found",
"status": "NOT_FOUND"
}
}
To debug this error, first check that the requested origin is publicly navigable. You can test this by entering the origin into your browser's address bar and comparing it against the final URL after any redirects. Common problems include unnecessarily adding or omitting the subdomain and using the wrong HTTP protocol.
{"origin": "http://www.web.dev"}
{"origin": "https://web.dev"}
If the requested origin is the navigable version, this error may also occur if the origin has an insufficient number of samples. All origins and URLs included in the dataset must have a sufficient number of samples to anonymize individual users. Additionally, origins and URLs must be publicly indexable. Refer to the CrUX methodology to learn more about how websites are included in the dataset.
Query URL data
You've seen how to query the CrUX API for the overall user experience on an origin. To restrict the results to a particular page, use the url
request parameter.
API_KEY="[YOUR_API_KEY]"
curl "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$API_KEY" \
--header 'Content-Type: application/json' \
--data '{"url": "https://web.dev/fast/"}'
This cURL command is similar to the origin example, except that the request body uses the url
parameter to specify the page to look up.
To query URL data from the CrUX API in JavaScript, call the CrUXApiUtil.query
function using the url
parameter in the request body.
CrUXApiUtil.query({
url: 'https://web.dev/fast/'
}).then(response => {
console.log(response);
}).catch(response => {
console.error(response);
});
If data for this URL exists in the CrUX dataset, the API will return a JSON-encoded response. For example
{
"record": {
"key": {
"url": "https://web.dev/fast/"
},
"metrics": {
"largest_contentful_paint": {
"histogram": [
{
"start": 0,
"end": 2500,
"density": 0.8477304539092148
},
{
"start": 2500,
"end": 4000,
"density": 0.08988202359528057
},
{
"start": 4000,
"density": 0.062387522495501155
}
],
"percentiles": {
"p75": 1947
}
},
// ...
}
}
}
True to form, the results show that https://web.dev/fast/
has 85% "good" LCP experiences and a 75th percentile of 1,947 milliseconds, which is slightly better than the origin-wide distribution.
URL normalization
The CrUX API may normalize requested URLs to better match the list of known URLs. For example, querying for the URL https://web.dev/fast/#measure-performance-in-the-field
will result in data for https://web.dev/fast/
due to normalization. When this happens, a urlNormalizationDetails
object will be included in the response.
{
"record": {
"key": {
"url": "https://web.dev/fast/"
},
"metrics": { ... }
},
"urlNormalizationDetails": {
"normalizedUrl": "https://web.dev/fast/",
"originalUrl": "https://web.dev/fast/#measure-performance-in-the-field"
}
}
Learn more about URL normalization in the CrUX documentation.
Query by form factor
User experiences can vary significantly depending on website optimizations, network conditions, and users' devices. To better understand these differences, drill down into origin and URL performance using the formFactor
dimension of the CrUX API.
The API supports three explicit form factor values: DESKTOP
, PHONE
, and TABLET
. In addition to the origin or URL, specify one of these values in the request body to restrict results to only those user experiences. This example demonstrates how to query the API by form factor using cURL.
API_KEY="[YOUR_API_KEY]"
curl "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$API_KEY" \
--header 'Content-Type: application/json' \
--data '{"url": "https://web.dev/fast/", "formFactor": "PHONE"}'
To query the CrUX API for form factor-specific data using JavaScript, call the CrUXApiUtil.query
function using the url
and formFactor
parameters in the request body.
CrUXApiUtil.query({
url: 'https://web.dev/fast/',
formFactor: 'PHONE'
}).then(response => {
console.log(response);
}).catch(response => {
console.error(response);
});
Omitting the formFactor
parameter is equivalent to requesting data for all form factors combined.
{
"record": {
"key": {
"url": "https://web.dev/fast/",
"formFactor": "PHONE"
},
"metrics": {
"largest_contentful_paint": {
"histogram": [
{
"start": 0,
"end": 2500,
"density": 0.778631284916204
},
{
"start": 2500,
"end": 4000,
"density": 0.13943202979515887
},
{
"start": 4000,
"density": 0.08193668528864119
}
],
"percentiles": {
"p75": 2366
}
},
// ...
}
}
}
The key
field of the response will echo back the formFactor
request configuration to confirm that only phone experiences are included.
Recall from the previous section that 85% of user experiences on this page had "good" LCP. Compare that to phone-specific experiences, of which only 78% are considered "good". The 75th percentile is also slower among phone experiences, up from 1,947 milliseconds to 2,366 milliseconds. Segmenting by form factor has the potential to highlight more extreme disparities in user experiences.
Assess Core Web Vitals performance
The Core Web Vitals program defines targets that help determine whether a user experience or a distribution of experiences can be considered "good". In the following example, we use the CrUX API and the CrUXApiUtil.query
function to assess whether a web page's distribution of Core Web Vitals metrics (LCP, FID, CLS) are "good".
CrUXApiUtil.query({
url: 'https://web.dev/fast/'
}).then(response => {
assessCoreWebVitals(response);
}).catch(response => {
console.error(response);
});
function assessCoreWebVitals(response) {
// See https://web.dev/vitals/#core-web-vitals.
const CORE_WEB_VITALS = [
'largest_contentful_paint',
'first_input_delay',
'cumulative_layout_shift'
];
CORE_WEB_VITALS.forEach(metric => {
const data = response.record.metrics[metric];
if (!data) {
console.log('No data for', metric);
return;
}
const p75 = data.percentiles.p75;
const threshold = data.histogram[0].end;
// A Core Web Vitals metric passes the assessment if
// its 75th percentile is under the "good" threshold.
const passes = p75 < threshold;
console.log(`The 75th percentile (${p75}) of ${metric} ` +
`${passes ? 'passes' : 'does not pass'} ` +
`the Core Web Vitals "good" threshold (${threshold}).`)
});
}
The results show that this page passes the Core Web Vitals assessments for all three metrics.
The 75th percentile (1973) of largest_contentful_paint passes the Core Web Vitals "good" threshold (2500).
The 75th percentile (20) of first_input_delay passes the Core Web Vitals "good" threshold (100).
The 75th percentile (0.05) of cumulative_layout_shift passes the Core Web Vitals "good" threshold (0.10).
Combined with an automated way to monitor API results, data from CrUX can be used to ensure that real-user experiences get fast and stay fast. For more information about Core Web Vitals and how to measure them, check out Web Vitals and Tools to measure Core Web Vitals.
What's next?
The features included in the initial version of the CrUX API only scratch the surface of the kinds of insights that are possible with CrUX. Users of the CrUX dataset on BigQuery may be familiar with some of the more advanced features including:
- Additional metrics
first_paint
dom_content_loaded
onload
time_to_first_byte
notification_permissions
- Additional dimensions
month
country
effective connection type
(ECT)
- Additional granularity
- detailed histograms
- more percentiles
Check out the official CrUX API docs to acquire your API key and explore more example applications. We hope you'll give it a try and we'd love to hear any questions or feedback you may have, so reach out to us on the CrUX discussion forum. And to stay up to date on everything we have planned for the CrUX API, subscribe to the CrUX announcement forum or follow us on Twitter at @ChromeUXReport.