Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
To detect the font a website actually renders, load the page in a real Chromium browser, wait for document.fonts.ready, inspect representative elements with getComputedStyle, enumerate the document’s FontFace objects, and (when possible) ask the Chrome DevTools Protocol which platform fonts rendered a node. Combine that evidence with @font-face declarations and network font responses. A CSS font-family stack alone only tells you the requested order, not the face that supplied the glyphs.
What a font-detection API should report
A useful audit separates three claims that are often incorrectly merged:
- Declared: a stylesheet contains a family or
@font-facerule. - Loaded: the browser knows a face and its status is loaded rather than failed or still loading.
- Observed rendered: the browser used a particular platform font for text in a specific node and environment.
Store the original family string, a normalized family name, source URL, weight, style, stretch, unicode range, load status, route, element, viewport, user agent, browser version, operating system and timestamp. Repeat the audit for important routes and states: responsive CSS, locale, shadow DOM, JavaScript-rendered content and interactions can all change font use.
Recommended Free Tools
Core browser pipeline
1. Render a controlled page
Use Chromium automation with a fixed viewport, user agent and locale. Visit the actual route, not only the home page, and trigger menus, dialogs or lazy sections whose text matters. Record the settings so another run is comparable.
#1 Best Overall
2. Wait for the font set
The CSS Font Loading API exposes the document’s FontFaceSet. The Document.fonts property returns that set, and document.fonts.ready resolves after loading work settles (see MDN’s Document.fonts reference and CSS Font Loading API guide). Waiting is essential: optional faces may never replace a fallback, and a face can be declared without being used.
3. Inspect computed styles
Collect fontFamily, fontSize, fontWeight, fontStyle and fontStretch from headings, body text, navigation, buttons and revealed content. Keep the complete, ordered fallback stack. The first name is not proof that it rendered.
4. Add node-level rendered evidence
Chromium’s DevTools Protocol CSS domain provides getPlatformFontsForNode where supported. It reports platform fonts used for a particular node, which is stronger evidence than a CSS declaration. The result is environment-specific, so retain browser, OS, viewport and user-agent details. See the Chrome DevTools Protocol CSS documentation.
5. Correlate declarations and resources
Parse accessible stylesheets for @font-face rules and log network responses for .woff2, .woff, .ttf and equivalent resources. Cross-origin stylesheets may block CSSOM access under the same-origin policy; network logs, computed styles and CDP inspection remain useful complementary evidence.
Complete Node.js example with Playwright
Install Playwright with npm install playwright, then save this script as font-audit.mjs. It captures loaded faces, computed stacks, font responses and (when available) platform-font data.
import { chromium } from 'playwright';
const target = process.argv[2] || 'https://example.com';
const browser = await chromium.launch();
const page = await browser.newPage({
viewport: { width: 1440, height: 1000 },
locale: 'en-US',
userAgent: 'FontAudit/1.0'
});
const fontResponses = [];
page.on('response', response => {
const type = response.request().resourceType();
const url = response.url().toLowerCase();
if (type === 'font' || /.(woff2?|ttf|otf)(?|$)/.test(url)) {
fontResponses.push({ url: response.url(), status: response.status() });
}
});
await page.goto(target, { waitUntil: 'networkidle', timeout: 90000 });
await page.evaluate(() => document.fonts.ready);
const report = await page.evaluate(() => {
const selectors = 'h1,h2,h3,p,body,nav,button,[role="button"],input,textarea';
const elements = [...document.querySelectorAll(selectors)].slice(0, 100);
const styles = elements.map((el, index) => {
const s = getComputedStyle(el);
return { index, tag: el.tagName, text: (el.textContent || '').trim().slice(0, 120),
fontFamily: s.fontFamily, fontSize: s.fontSize, fontWeight: s.fontWeight,
fontStyle: s.fontStyle, fontStretch: s.fontStretch };
});
const faces = [...document.fonts].map(face => ({ family: face.family,
style: face.style, weight: face.weight, stretch: face.stretch,
status: face.status, unicodeRange: face.unicodeRange, loaded: face.status === 'loaded' }));
const fontFaceRules = [];
for (const sheet of [...document.styleSheets]) {
try {
for (const rule of [...sheet.cssRules]) {
if (rule.type === CSSRule.FONT_FACE_RULE) fontFaceRules.push(rule.cssText);
}
} catch (_) { /* cross-origin CSSOM is not readable */ }
}
return { url: location.href, styles, faces, fontFaceRules,
viewport: { width: innerWidth, height: innerHeight }, userAgent: navigator.userAgent };
});
// CDP evidence for the first useful node.
const cdp = await page.context().newCDPSession(page);
let platformFonts = null;
try {
await cdp.send('DOM.enable');
await cdp.send('CSS.enable');
const node = await cdp.send('DOM.getDocument');
const found = await cdp.send('DOM.querySelector', { nodeId: node.root.nodeId, selector: 'h1,body' });
if (found.nodeId) platformFonts = await cdp.send('CSS.getPlatformFontsForNode', { nodeId: found.nodeId });
} catch (error) { platformFonts = { unavailable: String(error) }; }
console.log(JSON.stringify({ ...report, fontResponses, platformFonts }, null, 2));
await browser.close();
The script’s fontFamily values are CSS stacks. Treat faces as document-known declarations and load states, fontResponses as transport evidence, and platformFonts as node-level rendering evidence. For production, iterate through a curated selector list and save one record per route and state instead of truncating at 100 elements.
Finding and normalizing @font-face data
Each rule can specify a family, source URL, weight range, style, stretch, display mode and unicode range. Preserve the rule text as well as parsed fields: variable fonts may use ranges such as 100 900, and a unicode range can mean a face is used only for particular scripts. A network request proves that a resource was fetched, not that every visible glyph came from it.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallWhen a stylesheet is cross-origin, do not disable browser security merely to read it in production. Keep the network record and computed-style output, and, if you control the site, expose an audit endpoint or collect the stylesheet during a server-side build.
Rank #3
Google Fonts: enrichment after detection
The Google Fonts Developer API is a catalog lookup, not a detector. Query it only after page evidence identifies a family. Google documents stylesheet links and family, style, weight, subset and text parameters in its getting-started guide; its technical considerations explain that the Fonts API returns a user-agent-tailored stylesheet containing @font-face rules. A site may self-host a Google family, rename a local face or use a non-Google font, so catalog membership never proves page usage.
Accuracy limits and test design
- Fallbacks: unavailable or late-loading preferred faces let the browser render a later stack entry.
- Optional loading:
font-display: optionalcan leave a fallback in use even though a face is declared. - Dynamic content: menus, modals, shadow roots and injected text need explicit interaction and selectors.
- Responsive and locale differences: media queries and script coverage can select different faces.
- Environment dependence: platform-font results vary by OS, installed fonts, browser version, viewport and user agent.
- Privacy and operations: headless rendering sends the target URL and any configured headers/cookies through your infrastructure; define retention and access controls.
For high-confidence reports, run the same matrix of routes, viewport sizes, locales and interactions, then label each family as declared, loaded or observed rendered. Do not collapse those labels into one “website font” field.
Performance, reliability and cost choices
Browser startup and page rendering dominate latency and resource use. Reuse a browser process, cap concurrency, set navigation and overall timeouts, and cache immutable audit results by URL plus viewport, locale and user-agent. Network-idle is useful but not universal: pages with analytics or polling may never become quiet, so combine a bounded delay with document.fonts.ready and a selector that proves the content is present. Save failed responses and timeout reasons; a missing font request is itself valuable evidence.
Choose an implementation by rendered-font confidence, dynamic and cross-origin coverage, reproducibility across environments, headless operating cost, privacy handling and whether the report retains source URLs, weights, styles and failures. A lightweight CSS parser is cheaper but cannot establish rendering; Chromium plus CDP costs more but answers the question most accurately.
Rank #4
- Used Book in Good Condition
Troubleshooting common failures
Only generic families appear
The page may still be loading, the selector may be wrong, or the custom face failed. Wait for document.fonts.ready, inspect document.fonts statuses and check font-response status codes.
CSSOM throws a security error
The stylesheet is cross-origin. Keep computed styles and network logs, and parse the sheet from a permitted server-side context if you own it.
A declared family is not in the rendered result
It may be optional, limited by unicode range, unused on that element or superseded by a fallback. Test text containing the relevant script and inspect node-level platform fonts.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
CDP returns no platform fonts
Use Chromium, enable the DOM and CSS domains, resolve a real node ID and verify protocol support for your browser version. Report the limitation rather than treating computed CSS as rendered proof.
Best Value
Results change between runs
Pin browser version, viewport, locale and user agent; wait for the same state; record timestamps; and repeat until network and font states are stable. A different operating system can legitimately produce different platform-font names.
Or skip the browser setup
ScreenshotNeo is useful when you need a consistent rendered page capture alongside your font audit, although the screenshot itself does not replace document.fonts or CDP evidence. Its API accepts one GET request and supports PNG, JPEG, WebP or PDF output. Before capture it accepts the cookie/consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; those steps can be disabled individually. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
See the ScreenshotNeo API documentation for all options. A minimal call is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000, and every feature is available on every plan. Create a free ScreenshotNeo account to begin.
FAQ
Can an API identify a font from a screenshot alone?
Not reliably. A screenshot can support visual comparison, but browser-side declarations, load state and platform-font evidence are needed to distinguish similar families and fallbacks.
Should I report the first name in font-family?
No. Report the full ordered stack and label the result as CSS-declared unless you have loaded-face and rendered-node evidence.
How often should a font audit run?
Run it after typography or build changes and on a schedule appropriate to your site’s release frequency, covering representative routes and responsive states.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

