How to audit a font library for extension/format mismatches
- Step 1Inventory every font file — Walk `src/fonts`, `public/fonts`, and any design-token packages. For each `.ttf`/`.otf`/`.woff`/`.woff2`, record path, size, and declared extension.
- Step 2Read the magic for each — Drop each suspect into the identifier, or script the same 4-byte read. Capture `format`, `magic`, and `extension_matches_format`. For WOFF/WOFF2 also note `Wrapped flavor` and the wrapped/decompressed size.
- Step 3Classify each result — OK (extension matches), MISMATCH (valid font, wrong name), TTC (collection), or CORRUPT/UNKNOWN (no recognised magic or too short). Sort by status.
- Step 4Triage mismatches vs corrupt — Mismatches are usually safe to rename — the bytes are a valid sfnt. CORRUPT/UNKNOWN and truncated files need re-fetching from upstream; renaming won't help.
- Step 5Deduplicate by hash — Two files with the same name in different folders, or different names with identical bytes, both cause confusion. Hash each with /font-tools/font-fingerprinter to find true duplicates.
- Step 6Write the baseline and gate it — Output a CSV/JSON report, fix the findings, then wire a format check into CI so the clean baseline can't regress.
Audit report schema
One row per font file. The status column drives triage; the JAD tool's JSON gives you every field except status, which you derive from the comparison.
| Column | Source | Meaning |
|---|---|---|
path | filesystem | Where the asset lives |
declared | extension | What the filename claims (lower-cased) |
actual | magic bytes (format) | What the bytes really are |
magic | tool magic | The hex magic, e.g. 0x4F54544F |
status | derived | OK / MISMATCH / TTC / CORRUPT |
size | tool file_size_bytes | Useful for spotting truncations and duplicates |
Discrepancy classes and how to fix each
What you'll actually find in an inherited tree, and the correct remedy. Renaming fixes labels; re-fetching fixes broken bytes.
| Class | Signature | Fix |
|---|---|---|
| Harmless mislabel | extension_matches_format: false, but a valid sfnt magic | Rename file to match actual; update @font-face src |
| WOFF2 behind .woff | magic wOF2, extension .woff | Rename to .woff2, fix the format("woff2") hint |
| Stray collection | magic ttcf (format: ttc) | Split a face out on the desktop; replace the file |
| Truncated download | file < 4 bytes or format: unknown | Re-fetch from upstream foundry/CDN |
| Wrong asset entirely | magic of ZIP/PNG/HTML echoed | Delete; replace with the real font |
Cookbook
Realistic audit rows from inherited design systems (names anonymised). The JSON is the literal tool output; the status is what you'd record in the report.
Harmless mislabel — OTF named .ttf
ExampleValid OpenType font, wrong extension. Safe to rename; no re-fetch needed.
path: src/fonts/Brand-Bold.ttf
{ "format": "otf", "magic": "0x4F54544F", "extension_matches_format": false, "Tables": 13 }
status: MISMATCH → rename to Brand-Bold.otfThe Safari 404 culprit — WOFF2 behind .woff
ExampleBrotli bytes behind a .woff name. Older browsers feed it to a zlib decoder and the font silently vanishes.
path: public/fonts/Heading.woff
{ "format": "woff2", "magic": "0x774F4632", "extension_matches_format": false, "Wrapped flavor": "0x00010000", "Decompressed size": "288.6 KB" }
status: MISMATCH → rename to Heading.woff2, fix format("woff2")Mystery 24 MB 'font' — a collection
ExampleA TrueType collection committed under a .ttf name. Browsers can't load it.
path: public/fonts/Display.ttf (24.1 MB)
{ "format": "ttc", "magic": "0x74746366", "extension_matches_format": false }
status: TTC → split a face on the desktop, replaceDead download — truncated file
ExampleA few-KB stub from an interrupted download. Too short to hold a magic.
path: src/fonts/Subhead.woff2 (3 bytes)
{ "format": "unknown", "magic": "", "description": "Buffer is too short to identify." }
status: CORRUPT → re-fetch from upstreamTrue duplicate by hash
ExampleTwo differently-named files that are byte-identical. The format audit shows both as OK, but a hash audit reveals the duplication.
public/fonts/Inter.woff2 → format woff2, OK public/fonts/Inter-Regular.woff2 → format woff2, OK SHA-256 identical → keep one, delete the other (hash with /font-tools/font-fingerprinter)
Edge cases and what actually happens
Every row below was probed against the live API. Some documented requirements (alphabetical axis order, numerical tuple order) are not actually enforced in practice — useful to know if you've been blaming the wrong thing for a 400.
Mismatch that is actually fine
PreservedMany mismatches are harmless: the file is a valid sfnt, only the name is wrong. extension_matches_format: false does not mean corrupt — it means rename. Don't re-fetch a perfectly good font just because its label was off.
Two files, same name, different folders
Needs hashingFormat identification alone can't tell you whether public/fonts/icons.woff2 and src/icons.woff2 are the same font. Both report woff2. Hash them with /font-tools/font-fingerprinter to know whether one is stale.
A valid magic but the font is subtly corrupt
Not detected hereThe audit reads the header, not the glyph data. A font with correct magic but a damaged glyf/CFF table passes as OK. If renders look wrong, open it with /font-tools/font-metadata-extractor or compare a hash to upstream.
File over your tier size limit
Blocked in the browser toolThe interactive tool reads the whole file into memory before inspecting bytes, so a 60 MB font is blocked on free/Pro tiers. For large foundry archives, run the scripted 4-byte version (see the CI guide) which has no such cap.
Variable font reported as plain ttf/otf
By designThe magic doesn't reveal an fvar table, so a variable font reports ttf or otf. If your audit needs to flag variable fonts (e.g. for the freezer step), read metadata with /font-tools/font-metadata-extractor instead.
EOT/SVG fonts in a legacy folder
Out of scopeThe tool accepts only ttf/otf/woff/woff2, so .eot/.svg fonts won't upload. They're effectively dead on the modern web; the audit's recommendation is to replace them, not classify them.
Git LFS pointers instead of real fonts
Reads as unknownIf the repo uses LFS and the fonts weren't pulled, the on-disk files are tiny text pointers. The magic won't match — they'll classify as CORRUPT/UNKNOWN. Pull LFS objects before auditing, or you'll chase phantom problems.
Case-different extensions (.TTF, .OtF)
Handledextension_matches_format lower-cases the name before comparing, so Brand.TTF with TrueType magic correctly reports a match. Normalise extensions in your report so the status column is consistent.
Frequently asked questions
How common are format mismatches in real codebases?
More common than teams expect — an inherited design system often has a few percent of files where the extension doesn't match the magic. The usual cause is a download renamed to fit a naming convention without checking the bytes.
Is a mismatch always a bug?
No. Often the file is a valid sfnt and only the extension is wrong — renaming to match the detected format fixes it with no re-fetch. The damaging cases are WOFF2-behind-.woff (breaks old browsers) and stray TTCs (won't load).
What about WOFF2 files mislabelled as WOFF?
These break in browsers that lack WOFF2 support: the browser routes .woff/format("woff") to a zlib decoder, feeds it Brotli, and gets garbage. Rename to .woff2 and fix the format() hint in @font-face.
How do I find duplicate fonts?
Format identification can't — two copies both report the same format. Hash each file with /font-tools/font-fingerprinter and group by SHA-256 to find byte-identical duplicates shipped under different names.
Can the audit tell me if a font is variable?
No. The magic doesn't expose the fvar table, so variable fonts report ttf/otf. Read metadata with /font-tools/font-metadata-extractor to flag variable fonts, then freeze instances with /font-tools/variable-font-freezer if needed.
Will it catch a corrupt-but-correctly-magicked font?
No — it's a header check. A font with the right magic but a damaged body passes. Pair the format audit with a hash comparison or by actually opening the font in /font-tools/font-metadata-extractor.
What do I do with a stray TTC?
A TrueType collection can't be used directly on the web. Split out the face you need on the desktop (fonttools), then replace the file with the single-face TTF/OTF and re-run the audit.
The folder has a 60 MB font — can I audit it?
The interactive tool reads the whole file first, so large fonts hit the tier size cap (5 MB free, 50 MB Pro, 1 GB Developer). For oversized foundry archives, use the scripted 4-byte check from the CI guide, which has no cap.
How do I stop the mess from coming back?
Once your baseline is clean, wire a format check into CI so every PR is validated. See the CI guide for a copyable Node script and GitHub Actions step.
What columns should the report have?
path, declared (extension), actual (magic format), magic (hex), status (OK/MISMATCH/TTC/CORRUPT), and size. The tool's JSON gives you everything except status, which you derive from declared vs actual.
Are EOT and SVG fonts covered?
No — the tool accepts only ttf/otf/woff/woff2. EOT and SVG fonts are effectively dead on the modern web; the right move is to replace them, which the audit flags by their absence from the supported set.
Should I convert mismatched files or just rename?
Rename if the bytes already match a valid format (most mismatches). Only convert when you genuinely need a different format — for the web, convert to WOFF2 with /font-tools/ttf-to-woff2 rather than renaming.
Privacy first
Every JAD Font tool runs entirely in your browser using opentype.js and the wawoff2 WASM Brotli encoder. Your fonts never leave your device — verified by zero outbound network requests during processing.