ByteScope

WOFF2 font file format

Web Open Font Format 2 — a Brotli-compressed successor to WOFF.

MIME type
font/woff2
Extensions
.woff2
Category
Fonts

Magic-number signature

0x00000000774F4632wOF2'wOF2' tag

About the WOFF2 font format

WOFF2 keeps the shape of WOFF and replaces everything underneath it. The header grows to 48 bytes to make room for the size of the compressed body, the table directory becomes a variable-length structure that spells the common table tags as a single byte, the per-table zlib streams give way to one Brotli stream covering the whole font, and the glyf and loca tables are put through a reversible transform that re-encodes the outlines into a shape Brotli compresses far better. The font that comes back out is still equivalent to the one that went in, but unlike WOFF nothing inside can be read without decompressing all of it.

Header layout, byte by byte

OffsetLengthFieldMeaning
04signature77 4F 46 32, the ASCII letters wOF2. One character different from WOFF's tag, in the same position, which is exactly the kind of near-collision that makes a careless string comparison match the wrong format.
44flavorThe sfntVersion of the font inside: 00 01 00 00 for TrueType outlines or 4F 54 54 4F for CFF. Identical in meaning and position to WOFF's field, and the only place in the first forty-eight bytes that tells you anything about the font rather than about the wrapper.
84lengthTotal size of the WOFF2 file. As in WOFF it must match the file on disk exactly, which gives you a truncation check that costs nothing and needs no Brotli decoder.
122numTablesThe number of directory records. Unlike WOFF this does not tell you where the data begins, because the records are variable-length; you have to walk them.
142reservedMust be zero, and the browser sanitiser enforces it. Anything else here is a fast rejection on the web and a silently ignored value in most offline tools, which is a good example of why a font can behave differently in a page and on a desktop.
164totalSfntSizeThe size of the reconstructed sfnt font, used by a decoder to allocate the output buffer in one go. Ratios near three to one against the length field at offset 8 are ordinary for a Latin text face.
204totalCompressedSizeThe length of the Brotli stream. This is the field WOFF does not have, it is the reason the header is 48 bytes rather than 44, and it is the one number that lets a reader find the end of the compressed body without inflating any of it.
244majorVersion and minorVersionTwo 16-bit numbers at 24 and 26 versioning the font rather than the format, exactly as in WOFF but shifted by four bytes because of the field before them. Reading a WOFF2 with a WOFF parser produces plausible-looking nonsense from here on, which is a nastier failure than a clean rejection.
2812metaOffset, metaLength, metaOrigLengthThe optional compressed XML metadata block, described by three 32-bit fields at 28, 32 and 36. Usually all zero. When it is present it is compressed separately from the font body, so it can be read without touching the main stream.
408privOffset and privLengthThe optional private data block, at 40 and 44, closing out the 48-byte header. Defined by nothing and normally zero; a non-zero pair means whoever built the font put something of their own in the file.
48variableTable directoryVariable-length records, so unlike every other sfnt-derived format there is no arithmetic that gives you the address of the second one. Each record opens with a flags byte whose low six bits index a fixed list of well-known table tags - the value 63 meaning a literal four-byte tag follows instead - and whose top two bits carry a transform version. The lengths that follow are stored seven bits per byte with the high bit marking continuation, so a whole record can be as short as two bytes.
variablevariableBrotli streamEvery table, concatenated in directory order and compressed once. glyf and loca are transformed by default, and the encoding of that is genuinely counter-intuitive: for those two tags a transform version of 0 means transformed, while for every other tag 0 means untransformed. Third-party readers get this backwards regularly.

Telling it apart from its neighbours

Easily confused withHow to tell
WOFF fontThe tag at offset 0 is the quick test - 77 4F 46 32 against 77 4F 46 46 - and the structural difference is that WOFF2 inserts totalCompressedSize at offset 20, pushing majorVersion, minorVersion and the metadata fields four bytes later than WOFF puts them. A WOFF parser pointed at a WOFF2 does not crash; it reads a version number out of the compressed size and continues into garbage.
TrueType fontAn sfnt reader sees 77 4F 46 32 where it expects a version number and stops. The font's real sfntVersion is at offset 4, so the file does contain 00 01 00 00 or 4F 54 54 4F, just not where a font tool looks. Nothing short of a WOFF2 decoder will get the tables out.
Brotli (.br)Raw Brotli carries no signature bytes at all, so a .br file has nothing at offset 0 to recognise and is identified only by its filename or its Content-Encoding. A WOFF2 is not a Brotli file either: the first 48 bytes and the whole table directory are plain uncompressed structure, so feeding the file to a Brotli decompressor from offset 0 fails at once. The stream begins only after the directory, at an offset you can find only by walking it.

How these files break

  • A length field that disagrees with the file. The four bytes at offset 8 give the exact file size, so truncation is provable from the header. Do this before anything else; a large share of web-font failures end here.
  • A truncated Brotli stream. Unlike WOFF there is no per-table isolation - every table lives in one stream - so a few missing bytes at the end do not cost you one table, they cost you every table after the damage and in practice the entire font. There is no partially usable state to fall back to.
  • An error page under the font URL. A 404 or a login redirect saved as .woff2 gives the browser an HTML doctype where it wanted wOF2, and the console reports a decode failure and a sanitiser error rather than anything that mentions a web page.
  • A renamed file. A .woff2 downloaded from a font service and given a .ttf or .otf extension will not install anywhere, and the operating system reports the file as damaged rather than as the wrong format. The first four bytes settle it instantly.
  • Text-mode transfer or any line-ending rewrite. Brotli output is not text and does not survive having 0D 0A pairs changed, and because there is only one stream there is no salvageable remainder. The header still reads perfectly, which is what makes it confusing.

Where the format stands today

WOFF2 is the web-font format, with essentially no competition left. Every browser in current use supports it, Google Fonts and every other hosted service serve it by default, and a modern @font-face block that lists anything else is listing it for browsers that no longer appear in traffic. The saving over WOFF is around thirty percent for the same font, which in practice is the difference between a first paint that waits for the font and one that does not. Where it is never the right choice is anywhere the file has to be opened rather than rendered: it cannot be installed on a desktop, most font editors will not read it, and every inspection tool has to decompress the whole thing before it can say anything at all. Build pipelines therefore keep the TTF or OTF as the source of truth and emit WOFF2 as an output, the same way they keep source images and emit WebP - and a project that has lost the original and kept only the WOFF2 has made itself a genuine problem.

Open WOFF2 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 WOFF2 font — compare their leading bytes.

FormatFirst bytesExtension
WOFF font77 4F 46 46.woff
TrueType font00 01 00 00.ttf
OpenType font4F 54 54 4F.otf

Frequently asked questions

What is a magic number in a file?

The first few bytes of a file, which identify its format regardless of what the name says. A PNG always starts with 89 50 4E 47; a ZIP with 50 4B 03 04. Operating systems, browsers and the file command all read these bytes rather than trusting the extension, which is why renaming a file does not change what it actually is.

How do I check a file's magic number myself?

On macOS or Linux, xxd -l 16 yourfile prints the first 16 bytes; on Windows, Format-Hex -Path yourfile -Count 16 in PowerShell. Or drop the file into the File Type Identifier on this site, which reads the bytes locally and names the format for you — nothing is uploaded.

Why do two formats share the same magic number?

Because one is built on the other. Every docx, xlsx, pptx, apk, jar and epub begins with the ZIP signature, since all of them are ZIP archives underneath; telling them apart means looking at the entries inside. Java class files and macOS universal binaries both start with CA FE BA BE and are distinguished by the field that follows.

Can a file have no magic number at all?

Yes. Text-based formats — XML, HTML, JSON, SVG, CSV — have no fixed byte signature, so they are recognized structurally instead: a leading <?xml declaration, a <svg element, or a { followed by a quoted key. That detection is a good guess rather than a certainty, which is why this reference marks those formats as probe-detected.

Does a matching magic number mean the file is safe?

No. It tells you what the format is, not whether the contents are harmless — a genuine PDF or docx can still carry a malicious payload. Identifying the true type is a useful first check, especially when the extension disagrees with the bytes, but it is not a malware scan.

Have a file that might be in the WOFF2 font format? Identify it with the File Type Identifier — it reads the magic number locally, nothing is uploaded. Or browse all formats.