How to woff2 decompression: edge cases and common errors
- Step 1Read the magic before assuming format — The decompressor sniffs the first four bytes. `wOF2` (`77 4F 46 32`) is the only accepted input. `wOFF` (`77 4F 46 46`) is WOFF 1.0, `00 01 00 00` is TTF, `OTTO` is OTF, `ttcf` is a collection. Confirm with [font-format-identifier](/font-tools/font-format-identifier) if unsure.
- Step 2Classify the error string you got — `Expected a WOFF2 file but detected <FORMAT>` = the gate rejected it; the file is not WOFF2. `wawoff2 decompress failed` = the magic was fine but the Brotli payload is broken. The first is a naming/format problem; the second is a corruption problem.
- Step 3Predict the output flavour — After a successful Brotli decode, the tool reads the decompressed magic. `0x00010000`/`true` → TTF (`.ttf`, `font/ttf`); `OTTO` → OTF (`.otf`, `font/otf`). An `.otf` result is not a failure — the source wrapped CFF outlines.
- Step 4Handle a corruption failure — If `wawoff2` throws on a real `wOF2` file, the Brotli stream is truncated or bit-flipped. WOFF2 has no error correction; you cannot repair it locally. Re-download from the source and compare byte lengths against the server's Content-Length.
- Step 5Rule out a stripped-table theory — Decompression never removes tables. If a glyph, kerning pair, or licence string is missing from the output, it was missing from the source WOFF2 — verify by inspecting the original with [font-metadata-extractor](/font-tools/font-metadata-extractor) or [glyph-inspector](/font-tools/glyph-inspector).
- Step 6Check the size limit if blocked early — If the job is rejected before any decode with an upgrade prompt, the file is over your tier ceiling (5 MB free / 50 MB Pro / 1 GB Developer). That is a quota gate, not a format error.
The two error strings and what they mean
This decompressor emits exactly these two failures. The wording tells you whether to rename/re-source the file or re-download it.
| Error string | Stage it fires at | Root cause | Fix |
|---|---|---|---|
Expected a WOFF2 file but detected <FORMAT> | Magic-byte gate, before any decode | First 4 bytes are not wOF2 (file is WOFF/TTF/OTF/TTC) | Confirm the real format; the file does not need this tool |
wawoff2 decompress failed (ConvertWOFF2ToTTF returned false) | Inside the WASM Brotli decoder | Valid wOF2 magic but corrupt/truncated Brotli stream | Re-download; WOFF2 has no error correction |
| Upgrade prompt (no decode attempted) | Tier size check, before decode | File exceeds your tier's per-job ceiling | Upgrade tier, or subset the font first |
Buffer is too short to identify (unknown) | Magic sniff | File is under 4 bytes (empty/fragment download) | Re-download — the file is empty or partial |
Magic-byte decision matrix
What the four-byte signature tells the decompressor. Only wOF2 proceeds; the rest are rejected at the gate.
| Bytes | ASCII | Format | Result |
|---|---|---|---|
77 4F 46 32 | wOF2 | WOFF2 | Decompresses (the only accepted input) |
77 4F 46 46 | wOFF | WOFF 1.0 | Rejected — not WOFF2 (zlib, not Brotli) |
00 01 00 00 | — | TTF (TrueType sfnt) | Rejected — already a TTF |
4F 54 54 4F | OTTO | OTF (CFF sfnt) | Rejected — already an OTF |
74 74 63 66 | ttcf | TTC (collection) | Rejected — multi-face container |
74 72 75 65 | true | TTF (Apple variant) | Rejected — already TrueType |
Flavour detection: what extension you will get
After a successful decode the tool reads the decompressed magic to choose the extension and MIME. An OTF result is correct, not corruption.
| Decompressed magic | Outline type | Extension | MIME |
|---|---|---|---|
0x00010000 | TrueType (glyf/quadratic) | .ttf | font/ttf |
true | TrueType (Apple variant) | .ttf | font/ttf |
OTTO | CFF / PostScript (cubic) | .otf | font/otf |
Cookbook
Real inputs and the exact symptom each produces. Use these to match the error you are seeing to its cause.
WOFF mislabelled as WOFF2
ExampleThe single most common false alarm. An old build pipeline wrote a zlib WOFF and named it .woff2. The gate catches it before any decode and names the real format.
Input first bytes: 77 4F 46 46 (wOFF) Filename: brand.woff2 Error: Expected a WOFF2 file but detected WOFF. Diagnosis: it is WOFF 1.0, not WOFF2. The wrong extension is the only problem.
Truncated WOFF2 (valid magic, broken Brotli)
ExampleMagic is correct wOF2 so the gate passes, but the stream was cut short during download. The WASM decoder returns false.
Input first bytes: 77 4F 46 32 (wOF2) -- gate OK Byte length: 18,204 (server reported 41,016) Error: wawoff2 decompress failed (ConvertWOFF2ToTTF returned false). Diagnosis: half the file is missing. Re-download.
CFF WOFF2 decompresses to .otf
ExampleNot an error at all. The wrapped outlines are CFF, so the decompressed magic is OTTO and the tool writes .otf. People sometimes report this as a bug because the tool is called 'to TTF'.
Input: Lora-Regular.woff2 (wOF2) Decompressed magic: 4F 54 54 4F (OTTO) Result panel: Decompressed format: OTF Inflated size: 176.2 KB Download: Lora-Regular.otf (correct)
A TTC renamed to .woff2
ExampleSomeone bundled a multi-face collection and gave it a web extension. WOFF2 only wraps one face, so the ttcf magic is rejected.
Input first bytes: 74 74 63 66 (ttcf) Filename: fonts.woff2 Error: Expected a WOFF2 file but detected TTC. Diagnosis: it is a TrueType Collection. Split it into single faces before any web conversion.
Empty / zero-byte file
ExampleA failed download left a 0-byte file. There is nothing to sniff, so it is reported as unidentifiable rather than as WOFF2.
Input byte length: 0 Detection: format = unknown (buffer too short) Result: not treated as WOFF2; nothing to decompress. Fix: re-download — the transfer never completed.
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.
Valid magic, dead Brotli stream
Decompression failedThe four-byte wOF2 header survives a truncation or bit-flip, so the gate passes, but the compressed body cannot be decoded and wawoff2 returns false → wawoff2 decompress failed (ConvertWOFF2ToTTF returned false). WOFF2 carries no checksums over the Brotli payload and no redundancy, so partial recovery is impossible. Re-download from the original source.
Output is .otf and the user expected .ttf
By designWhen the decompressed magic is OTTO, the source wrapped CFF/PostScript outlines and the tool correctly writes .otf with font/otf. This is the right container — a CFF font forced to a .ttf name fails in strict installers. The tool name reflects the common (TrueType) case, not a guarantee of TTF output.
WOFF 1.0 mislabelled .woff2
Expected — detected WOFFWOFF uses wOFF and zlib per-table compression; WOFF2 uses wOF2 and a single Brotli stream with glyf preprocessing. They are not interchangeable. This tool only handles wOF2, so a WOFF is rejected with Expected a WOFF2 file but detected WOFF. The fix is to treat the file as WOFF — the extension is simply wrong.
Bare TTF or OTF given a .woff2 name
Expected — already sfntAn uncompressed sfnt (0x00010000 or OTTO) named .woff2 is rejected because there is nothing to decompress — it is already the format you want. Just rename it to .ttf/.otf. Confirm with font-format-identifier.
TrueType Collection wrapped/renamed as WOFF2
Rejected — detected TTCWOFF2 holds a single face. A ttcf collection cannot be a WOFF2 and is rejected at the gate. There is no per-face extraction in this tool — split the TTC into individual faces first, then compress each separately if you still need WOFF2.
Nothing was stripped — the table was never there
PreservedDecompression is lossless; it never removes name, kern, GSUB/GPOS, hinting, or any other table. If something is missing from the output it was missing from the source WOFF2 (often because the font was subset before being compressed). Inspect the original to confirm, rather than blaming the decompressor.
Decompressed hash differs from the upstream TTF
Preserved (functionally)WOFF2 normalises table order and padding, so the decompressed sfnt's SHA-256 can differ from the foundry's original TTF while rendering identically. This is expected for any WOFF2 round-trip and is not a sign of data loss. Verify equivalence by rendering or by comparing glyph counts, not by hashing.
File rejected before any decode with an upgrade prompt
Rejected — over tier limitThe size check runs before decompression. A file over 5 MB (free), 50 MB (Pro), or 1 GB (Developer) is blocked with an upgrade message — not a format error. Because WOFF2 is already compressed, this almost only happens with very large CJK fonts on the free tier.
WASM module needs a fresh page load
Transient — reloadThe wawoff2 decompress binding is cached after first use. In the rare case the binding fails to expose its decompress function (a partial module load), reloading the page re-instantiates the WASM module and resolves it. This is environmental, not a property of your font.
Embedded fonts / multiple faces in one file
Not supported by the formatWOFF2 does not have a multi-face container the way TTC does. If you believe a single file holds several faces, it is a collection (ttcf), not a WOFF2 — and it is rejected here. There is no "face index" option to pick a subface.
Frequently asked questions
Why does my WOFF2 decompress to OTF instead of TTF?
The decompressed magic is OTTO, which means the font has CFF/PostScript outlines — it was originally an OTF, wrapped in a WOFF2 container. The tool reads that magic and writes .otf with font/otf. It is not corrupt and you should not rename it to .ttf; use it as the OTF it is.
What does 'wawoff2 decompress failed' mean exactly?
It means the file had a valid wOF2 magic (so it really is a WOFF2) but the Brotli-compressed body could not be decoded — the stream is corrupt or truncated. The most common cause is an incomplete download. WOFF2 has no error correction, so a single bad byte is fatal; re-fetch the file and compare its byte length to the server's reported size.
What does 'Expected a WOFF2 file but detected …' mean?
The magic-byte gate ran before any decompression and found the first four bytes were not wOF2. The named format (WOFF, TTF, OTF, TTC) is what the file actually is. This is a naming/format problem, not corruption — the file simply is not a WOFF2 and does not need this tool. Confirm with font-format-identifier.
How do I tell WOFF2 from WOFF without a tool?
Look at the first four bytes. WOFF2 is 77 4F 46 32 (wOF2); WOFF 1.0 is 77 4F 46 46 (wOFF). They differ only in the last byte. WOFF2 uses a single Brotli stream with glyf preprocessing; WOFF compresses each table with zlib. This decompressor only accepts wOF2.
Can I decompress a WOFF2 that contains multiple fonts?
No — WOFF2 wraps exactly one face. A file holding multiple faces is a TrueType Collection with the ttcf magic, not a WOFF2, and it is rejected at the gate. Split the collection into single faces first; there is no face-index picker in this tool.
Is a corrupt WOFF2 recoverable?
Not from the WOFF2 alone. The format has no error-correction or redundancy, so a truncated or bit-flipped Brotli stream is unrecoverable. Your only fix is to obtain a clean copy. If you have the original TTF/OTF, you can re-create the WOFF2 with ttf-to-woff2.
Why is the decompressed file bigger than I expected?
WOFF2 typically compresses Latin fonts to 30–40% of the raw sfnt, so 2.5–3.5× inflation is normal — Brotli plus glyf-table preprocessing accounts for it. CJK fonts can inflate even more. The Inflated size metric is informational; a large value is expected, not a sign of a problem.
Did the decompressor strip my kerning / licence / hinting?
No. Decompression is lossless and never removes tables — kern, GSUB/GPOS, the name table (licence, copyright, vendor), and hinting (fpgm/prep/cvt ) all survive. If any of those is missing from the output, it was already absent from the source WOFF2. Inspect the original to confirm.
Is there a size limit on the input?
Yes: 5 MB on the free tier, 50 MB on Pro, 1 GB on Developer. The size check happens before decompression, so an over-limit file is blocked with an upgrade prompt rather than a format error. WOFF2 is small, so this only bites on very large CJK fonts on free.
The page seemed to hang on a big CJK font — is that a failure?
Not necessarily. Large CJK fonts have tens of thousands of glyphs, and Brotli decode plus glyf reconstruction takes proportionally longer than a small Latin font. As long as the file is under your tier limit and not corrupt, let it finish. If it genuinely errors, the message will be one of the two documented strings.
Does decompression happen locally?
Yes. The wawoff2 WebAssembly module runs in your browser; the font bytes are not uploaded. Only an anonymous run counter is recorded server-side for dashboard stats. Paid tiers may route the job to a local @jadapps/runner, which is also on your own machine.
Where can I read about WOFF2's internal structure?
See WOFF & WOFF2 spec internals explained for the container layout, the Brotli stream, and the glyf-table transform that makes WOFF2 smaller than WOFF. That guide explains why one bad byte breaks the whole file.
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.