WOFF font file format
Web Open Font Format — a compressed wrapper around TTF/OTF for the web.
- MIME type
- font/woff
- Extensions
- .woff
- Category
- Fonts
Magic-number signature
| 0x00000000 | 774F4646 | wOFF | 'wOFF' tag |
About the WOFF font format
WOFF is not a font format of its own - it is a container. It takes a complete TrueType or OpenType font, compresses each of its tables separately with zlib, and puts a 44-byte header and a new table directory in front, and the font that comes back out is byte-equivalent to the one that went in. Two things motivated it: the size saving, and the fact that a WOFF is visibly a web font rather than an installable desktop file, which was enough to persuade type foundries to license their faces for @font-face at all. It also carries an optional block of compressed XML metadata intended for exactly that licensing information.
Header layout, byte by byte
| Offset | Length | Field | Meaning |
|---|---|---|---|
| 0 | 4 | signature | 77 4F 46 46, the ASCII letters wOFF with a lower-case w. The mixed case is deliberate and worth remembering, because a case-insensitive search for WOFF in a hex viewer will also match text in the metadata block and in any embedded copyright string. |
| 4 | 4 | flavor | The sfntVersion of the font inside: 00 01 00 00 for TrueType outlines, 4F 54 54 4F for CFF. This is what a .ttf or .otf carries at offset 0, displaced by four bytes, and it tells you what you will be holding once the wrapper comes off. |
| 8 | 4 | length | Total size of the WOFF file in bytes. It must equal the actual file length, which makes a truncated download detectable from the first twelve bytes alone, with no decompression and no font knowledge at all - one of the very few formats that hands you that check for free. |
| 12 | 2 | numTables | How many entries the table directory holds. Every entry is exactly twenty bytes, so this number also tells you where the compressed data starts. |
| 14 | 2 | reserved | Must be zero. Browsers run every web font through a sanitiser before handing it to the rasteriser, and a non-zero value in a field nothing reads is one of the things that gets a file rejected outright on the web while desktop tools open it happily. |
| 16 | 4 | totalSfntSize | The size of the reconstructed font, including its directory and the padding between tables. Set against the length field at offset 8 it gives you the compression ratio without unpacking anything; a little better than two to one is typical for a text face. |
| 20 | 4 | majorVersion and minorVersion | Two 16-bit numbers, at offsets 20 and 22. They version the font, not the WOFF format, and are usually copied from or aligned with the source font's head table; values of 1.0 and 1.1 are what you will mostly see. |
| 24 | 12 | metaOffset, metaLength, metaOrigLength | Three 32-bit fields at 24, 28 and 32 describing an optional block of zlib-compressed XML metadata - the place the licence, vendor and description are meant to live. All three read zero when there is none, which is the usual case in practice. |
| 36 | 8 | privOffset and privLength | A second optional block, at 36 and 40, reserved for whatever the font's creator wants to put there and defined by nothing. Also normally zero, and a non-zero pair is worth looking at when you are trying to work out where a font came from. |
| 44 | variable | Table directory | Twenty bytes per table, starting immediately after the header: tag, offset into the WOFF file, compressed length, original length, and the checksum of the original uncompressed table. The first table's compressed data therefore begins at 44 plus twenty times numTables, an arithmetic you can do in your head at the hex viewer. |
| variable | variable | Compressed table data | One independent zlib stream per table, in directory order. When compressing a table would not make it smaller the format allows it to be stored as-is, and you can spot that in the directory as an entry whose compressed length equals its original length - common for tiny tables of a few dozen bytes. |
Telling it apart from its neighbours
| Easily confused with | How to tell |
|---|---|
| WOFF2 font | 77 4F 46 46 against 77 4F 46 32 at offset 0, and everything after that differs too: the WOFF2 header is 48 bytes rather than 44 because it adds a totalCompressedSize field at offset 20, its directory records are variable-length rather than a fixed twenty bytes, and its body is one Brotli stream for the whole font instead of one zlib stream per table. |
| TrueType font | The font inside a WOFF is a TrueType or OpenType font and nothing has been altered about it, but the four bytes that identify it have moved from offset 0 to offset 4. A reader that only checks offset 0 sees wOFF and reports an unknown font; a reader that checks offset 4 as well can tell you which flavour it will get after inflating. |
| GZIP | Both carry zlib-compressed payloads and neither is readable by pointing a generic decompressor at offset 0. A gzip file is 1F 8B and holds one stream that starts near the front; a WOFF holds many streams that start at offsets only the directory knows, with no local header in front of any of them. Renaming a WOFF to .gz and running gunzip on it fails immediately, and that failure says nothing about whether the font is intact. |
How these files break
- A length field that disagrees with the file. The four bytes at offset 8 state the whole file size, so a truncated or padded download is provable in a hex viewer before anything is decompressed. This is the first check to run and it resolves a large share of failed web-font loads on its own.
- An error page under the font URL. A 404, a login redirect or a rate-limit notice saved as a .woff opens in a text editor as HTML, and the browser console reports a failure to decode the downloaded font along with a sanitiser parsing error. Nothing about that message suggests the file is a web page, which is why it wastes so much time.
- Rejection by the browser's sanitiser. Browsers are considerably stricter than desktop font tools: reserved fields that are not zero, fields out of their permitted range and structures that overlap will all be refused on the web by a file that installs cleanly on your own machine. A font that works locally and not in a page is usually this, not a server problem.
- One damaged table. Because every table has its own zlib stream, a single flipped byte kills exactly one of them and leaves the rest intact. That produces the strange failure where a font loads, reports correct metrics and draws glyphs, but has lost its kerning or its vertical metrics - whichever table failed to inflate.
- Text-mode transfer. A deployment step that translates line endings rewrites 0D 0A inside the compressed streams, which are not text and do not survive it. The header and directory come through unharmed, so the file still looks like a font right up to the point where inflate fails.
Where the format stands today
WOFF was the web-font format from roughly 2012 until WOFF2 caught up, and its job now is compatibility rather than delivery. Every browser released in the last decade prefers WOFF2, which is about a third smaller for identical content, so a modern @font-face rule lists WOFF2 first and WOFF only as a fallback - and increasingly not even that, because the browsers that would need the fallback have essentially vanished from analytics. Where WOFF still shows up is inside packaged software, documentation bundles and themes that were built years ago and never rebuilt, and in tooling that predates Brotli. Its real achievement was never technical. By wrapping a font in something that clearly was not installable, and by carrying a metadata block where a licence could travel with the file, it settled the argument that had kept foundries from allowing web use at all, and every web font since has been living off that settlement.
Open WOFF font files on this site
Every one of these runs in your browser — the file never leaves your machine.
Related formats
Formats that are easy to confuse with WOFF font — compare their leading bytes.
| Format | First bytes | Extension |
|---|---|---|
| WOFF2 font | 77 4F 46 32 | .woff2 |
| TrueType font | 00 01 00 00 | .ttf |
| OpenType font | 4F 54 54 4F | .otf |