Chrome Extension Core Web Vitals (2026)
Chrome Extension Core Web Vitals Checker: Developer Guide
Core Web Vitals have become the standard for measuring web performance and user experience. Building a Chrome extension that checks these metrics gives you real-time insights without leaving your browser. This guide walks you through creating a functional Core Web Vitals checker extension from scratch.
What Are Core Web Vitals
Core Web Vitals consist of three metrics that Google uses to evaluate page experience:
- Largest Contentful Paint (LCP) measures loading performance. A good LCP is under 2.5 seconds.
- First Input Delay (FID) measures interactivity. A good FID is under 100 milliseconds.
- Cumulative Layout Shift (CLS) measures visual stability. A good CLS is under 0.1.
Building an extension to track these metrics requires understanding the Chrome APIs available for performance measurement and how to extract meaningful data from them.
Extension Architecture
Your Core Web Vitals checker will need three main components:
- Content script - Injected into pages to collect performance data
- Background worker - Handles message passing and data aggregation
- Popup UI - Displays metrics to users in a clean interface
The content script uses the Performance API to gather metrics, then communicates with the popup through Chrome’s message passing system.
Setting Up the Manifest
Every Chrome extension starts with the manifest file. For a Core Web Vitals checker, you need Manifest V3 with specific permissions:
{
"manifest_version": 3,
"name": "Core Web Vitals Checker",
"version": "1.0.0",
"description": "Measure Core Web Vitals on any page",
"permissions": [
"activeTab",
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png"
}
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_idle"
}]
}
The activeTab permission lets your extension interact with the currently active tab, while scripting allows you to execute content scripts. The host permissions <all_urls> grants access to measure performance on any website.
Collecting Performance Metrics
The content script is where the actual measurement happens. You’ll use the Performance API to extract Core Web Vitals data:
// content.js
function getCoreWebVitals() {
return new Promise((resolve) => {
if (!window.PerformanceObserver) {
resolve(null);
return;
}
const metrics = {};
let observer;
// Measure LCP
try {
observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
metrics.lcp = Math.round(lastEntry.renderTime || lastEntry.loadTime);
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
} catch (e) {
console.log('LCP not supported');
}
// Measure CLS
try {
const clsObserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!(entry as any).hadRecentInput) {
metrics.cls = (metrics.cls || 0) + (entry as any).value;
}
}
});
clsObserver.observe({ type: 'layout-shift', buffered: true });
} catch (e) {
console.log('CLS not supported');
}
// Get FID from event timing
const paintEntries = performance.getEntriesByType('paint');
const fcpEntry = paintEntries.find(e => e.name === 'first-contentful-paint');
if (fcpEntry) {
metrics.fcp = Math.round(fcpEntry.startTime);
}
// Return metrics after a delay to ensure collection
setTimeout(() => {
resolve(metrics);
}, 2000);
});
}
// Listen for messages from popup
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === 'getMetrics') {
getCoreWebVitals().then(sendResponse);
return true;
}
});
This script uses PerformanceObserver to watch for Largest Contentful Paint and layout shift events. The CLS calculation filters out entries with recent user input, as those shouldn’t count toward cumulative shift. The script returns metrics after a 2-second delay to ensure enough time for all paint events to fire.
Building the Popup Interface
The popup provides the user-facing interface. It requests metrics from the content script and displays them with visual indicators:
<!-- popup.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body { width: 300px; padding: 16px; font-family: system-ui, sans-serif; }
.metric { margin-bottom: 12px; padding: 12px; border-radius: 8px; background: #f5f5f5; }
.metric.good { border-left: 4px solid #34d399; }
.metric.needs-improvement { border-left: 4px solid #fbbf24; }
.metric.poor { border-left: 4px solid #f87171; }
.metric-label { font-size: 12px; color: #666; }
.metric-value { font-size: 24px; font-weight: bold; margin-top: 4px; }
h2 { margin: 0 0 16px 0; font-size: 18px; }
.refresh { width: 100%; padding: 8px; background: #3b82f6; color: white; border: none; border-radius: 4px; cursor: pointer; }
.refresh:hover { background: #2563eb; }
</style>
</head>
<body>
<h2>Core Web Vitals</h2>
<div id="metrics"></div>
<button class="refresh" id="refreshBtn">Refresh Metrics</button>
<script src="popup.js"></script>
</body>
</html>
Handling Metric Display Logic
The popup JavaScript bridges the gap between the content script and the UI:
// popup.js
function getRating(value, type) {
if (type === 'lcp') {
return value < 2500 ? 'good' : value < 4000 ? 'needs-improvement' : 'poor';
} else if (type === 'fid') {
return value < 100 ? 'good' : value < 300 ? 'needs-improvement' : 'poor';
} else if (type === 'cls') {
return value < 0.1 ? 'good' : value < 0.25 ? 'needs-improvement' : 'poor';
}
return 'needs-improvement';
}
function displayMetrics(metrics) {
const container = document.getElementById('metrics');
container.innerHTML = '';
const metricDefinitions = [
{ key: 'lcp', label: 'Largest Contentful Paint', unit: 'ms' },
{ key: 'cls', label: 'Cumulative Layout Shift', unit: '' },
{ key: 'fcp', label: 'First Contentful Paint', unit: 'ms' }
];
metricDefinitions.forEach(def => {
const value = metrics[def.key];
if (value === undefined) return;
const rating = getRating(value, def.key);
const displayValue = def.unit ? `${value}${def.unit}` : value.toFixed(3);
const div = document.createElement('div');
div.className = `metric ${rating}`;
div.innerHTML = `
<div class="metric-label">${def.label}</div>
<div class="metric-value">${displayValue}</div>
`;
container.appendChild(div);
});
}
async function fetchMetrics() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab || !tab.id) return;
try {
const response = await chrome.tabs.sendMessage(tab.id, { action: 'getMetrics' });
if (response) {
displayMetrics(response);
}
} catch (error) {
document.getElementById('metrics').innerHTML =
'<p>Unable to fetch metrics. Try refreshing the page.</p>';
}
}
document.getElementById('refreshBtn').addEventListener('click', fetchMetrics);
fetchMetrics();
The rating function applies Google’s official thresholds for each metric, coloring the results green for good, yellow for needs improvement, and red for poor performance. The fetchMetrics function queries the active tab and requests performance data from the content script.
Testing Your Extension
To test the extension locally:
- Open Chrome and navigate to
chrome://extensions/ - Enable “Developer mode” in the top right corner
- Click “Load unpacked” and select your extension folder
- Visit any website and click the extension icon to see metrics
For debugging, check the popup console and the background service worker console in the extensions page. PerformanceObserver can be finicky, so test across multiple sites to ensure reliable data collection.
Limitations and Considerations
Limitations and Considerations captures metrics at the time of measurement, which differs from field data that Chrome’s CrUX reports. Your extension provides lab data, a snapshot of performance under specific conditions. For comprehensive analysis, combine your extension with PageSpeed Insights or Chrome DevTools.
Some Single Page Applications may not trigger fresh LCP events on navigation, requiring users to manually refresh after content changes. The extension works best on traditional multi-page sites where full page loads occur.
Building this extension gives you a practical tool for quick performance audits while learning the Chrome extension APIs and Performance API in depth.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Chrome Web Vitals Optimization: A Practical Guide for.
- AI Bookmark Manager for Chrome: Organizing Your Web Knowledge
- AI Grammar Checker Chrome Extension: A Developer’s Guide
- SEO Checker Chrome Extension Guide (2026)
- Diff Checker Chrome Extension Guide (2026)
- Plagiarism Checker Free Chrome Extension Guide (2026)
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.