The basics
What is a WOFF2 file?
A WOFF2 file is a font, packaged for the web. Inside is an ordinary TrueType or OpenType font, the same kind you would install on a computer, compressed so that a browser can download it quickly when a page asks for it. Once the browser has unpacked it, the font is used exactly like the original: same letters, same spacing, same features.
It is the format to use for custom fonts on a website today. Every current browser supports it, and it is usually the smallest of the font formats. Google Fonts and most other web font services deliver WOFF2 to current browsers.
A few things it is not:
- Not a different font design. Converting a TTF or OTF to WOFF2 and back gives you a working font with the same glyphs and metrics. Only the packaging changes.
- Not something you double-click. Browsers don't display font files on their own, and Windows and macOS don't install WOFF2 directly. To use the font in desktop apps, convert it to TTF or OTF first, if its licence allows.
- Not a new licence. A WOFF2 file is as protected, or as free, as the font it came from. The format adds no copy protection.
Anatomy
What's inside a WOFF2 file
A WOFF2 file is a 48-byte header, a compact table directory, and one Brotli-compressed block holding every table of the original font. Below is our sample-regular.woff2, byte by byte. Numbers in the header are big-endian: 00 00 11 4C is 0x114C, which is 4,428.
offset bytes meaning
000000 77 4F 46 32 signature "wOF2"
000004 00 01 00 00 flavor: TrueType outlines
000008 00 00 11 4C length: 4,428 bytes
00000C 00 0A 10 tables
00000E 00 00 reserved
000010 00 00 2D C4 decoded font size: 11,716
000014 00 00 10 FF compressed data: 4,351
000018 00 01 00 00 font version 1.0
00001C 00 00 00 00 … (20 bytes) no metadata, no private data
000030 06 60 table: OS/2, 96 bytes
000032 00 82 06 table: cmap, 262 bytes
000035 0A BE 4C AD 69 table: glyf, 8,012 → 5,865
00003A 01 36 table: head, 54 bytes
00003C 02 24 table: hhea, 36 bytes
00003E 03 83 0C table: hmtx, 396 bytes
000041 0B 81 48 00 table: loca, 200 → 0
000045 04 20 table: maxp, 32 bytes
000047 05 91 29 table: name, 2,217 bytes
00004A 07 81 68 table: post, 232 bytes
00004D … one Brotli stream, 4,351 bytes
00114C end of file: 4,428 bytes- 1
77 4F 46 32The signature
The ASCII letters
wOF2. Browsers and tools check these four bytes, not the file extension, to recognise the format. - 2
00 01 00 00The flavor
What kind of font is inside:
00 01 00 00for TrueType outlines,OTTOfor CFF outlines,ttcffor a font collection. Decoding gives back a font that starts with exactly these bytes. - 3
00 00 2D C4 / 00 00 10 FFTwo sizes
11,716 bytes is how big the font will be once decoded; 4,351 bytes is the compressed block. A decoder can allocate memory before it starts.
- 4
00 00 00 00 … (20 bytes)Optional blocks
Offsets and lengths for an extended metadata block (XML licence and credits) and a private data block. Both are zero here; browsers ignore both anyway.
- 5
06 60A table entry
One flags byte and a length. The low 6 bits of
06are an index into a fixed list of 63 common table names (6 isOS/2), so common tables need no 4-byte name.60is the length, 96, written in a variable-length format called UIntBase128. - 6
0A BE 4C AD 69The transformed glyf table
Index 10 is
glyf, the glyph outlines, with transform bits 0, meaning transformed.BE 4Cis the original length (8,012 bytes) andAD 69the transformed length (5,865). The transform splits points, flags and contour counts into separate streams that compress better. - 7
0B 81 48 00The loca table
localists where each glyph starts inglyf. Whenglyfis transformed,locais stored with a length of 0 and rebuilt by the decoder from the glyphs themselves. - 8
00004D …The compressed data
All ten tables, concatenated in directory order and compressed together as a single Brotli stream. WOFF 1.0 compressed each table separately with zlib, which is one reason WOFF2 files are smaller.
Under the hood
How WOFF2 makes fonts smaller
A desktop font is a collection of tables, each with a four-letter name: cmap maps characters to glyphs, glyf or CFF holds the outlines, hmtx the widths, name the font's names and licence, and so on. WOFF2 keeps all of them but stores them more compactly, in three steps:
- 1A shorter directory. Common table names are stored as a 6-bit number instead of four letters, and lengths use a variable-length number format, so small tables take two bytes to describe.
- 2Transforms. The
glyftable is reorganised so that similar data sits together: all contour counts, then all point counts, then all flags, then all coordinates. Compressors find repeating patterns more easily that way. Thelocatable is dropped and recomputed, and thehmtxtable can optionally drop side bearings that can be worked out from the outlines. - 3Brotli compression. Everything is compressed as one stream with Brotli, a compression format published by Google (RFC 7932), which has a built-in dictionary and handles small files well.
The W3C's evaluation, made while the format was designed, measured median savings over WOFF 1.0 of about 24 to 27% for TrueType fonts and about 13% for fonts with CFF outlines. Against the uncompressed desktop font the saving is bigger still, as our own samples show:
| Format | Sample file | Size |
|---|---|---|
| TTF (uncompressed) | sample-regular.ttf | 11,588 bytes |
| OTF with CFF outlines | sample-regular.otf | 12,172 bytes |
| WOFF (zlib) | sample-regular.woff | 6,092 bytes |
| WOFF2 (Brotli) | sample-regular.woff2 | 4,428 bytes |
Lossless, with one subtlety
WOFF2 is lossless in what matters: the decoded font draws exactly the same glyphs. It is not always byte-for-byte identical to the input, because the glyf transform may rebuild loca and pad glyphs differently. Font checksums are recalculated, so tools treat the result as a valid font.
On the web
Loading a WOFF2 font with @font-face
A web page uses a font file through a CSS @font-face rule, which gives the font a name and says where to download it. Each weight and style is its own file and its own rule, joined by the shared font-family name:
@font-face {
font-family: "99helpers Sample";
src: url("/fonts/sample-regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "99helpers Sample";
src: url("/fonts/sample-bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
body {
font-family: "99helpers Sample", system-ui, sans-serif;
}format("woff2")tells the browser what the file is before it downloads it. A browser that can't read a format skips that source and tries the next one in the list.- Do you still need a WOFF fallback? Only for very old browsers. Every current browser reads WOFF2, so most sites now list WOFF2 alone. Older guides add WOFF, TTF and an EOT file for Internet Explorer; that is no longer needed.
font-weightandfont-stylemust match the file. If you declare only a regular file and use bold text, the browser fakes bold by thickening the letters, which looks smudged.font-displaycontrols what visitors see while the font downloads (see below).
Fonts are only requested once the browser finds text that needs them, which happens late, after the CSS has loaded. For the one or two fonts used above the fold, a preload hint starts the download straight away. The crossorigin attribute is required even for fonts on your own site, because browsers always fetch fonts in CORS mode:
<link rel="preload" href="/fonts/sample-regular.woff2"
as="font" type="font/woff2" crossorigin>Troubleshooting
Why isn't my web font loading?
- Blocked by CORS. The font is on a different domain from the page (a CDN, a separate storage bucket) and that server doesn't send an
Access-Control-Allow-Originheader. The CSS Fonts specification requires browsers to fetch fonts in CORS mode, so the request fails and the console shows a CORS error. Fix it on the server that hosts the font, or host the font on the same site as the page. - Wrong path. URLs inside a CSS file are relative to the CSS file, not to the page.
url(fonts/x.woff2)in/css/style.csspoints to/css/fonts/x.woff2. - Wrong MIME type. Browsers detect the format from the file's bytes, so a generic type such as
application/octet-streamusually still works.font/woff2is the correct one, and audit tools flag the others. - A file that isn't really WOFF2. A file renamed from
.ttfto.woff2has the wrong signature and is rejected, or works only by accident. Check the first four bytes: they must bewOF2. - Invisible text, then a sudden swap. That is the browser waiting for the font: invisible text is called FOIT (flash of invisible text), a visible fallback that changes later is FOUT (flash of unstyled text).
font-displaydecides which you get. - Rejected as invalid. Chrome and Firefox run every web font through the OpenType Sanitizer before use, and drop fonts with broken tables that a desktop app might tolerate. The browser console says so.
| Value | While loading | If the font arrives late |
|---|---|---|
auto | Browser's choice, usually like block | Browser's choice |
block | Invisible text for up to about 3 seconds | Swaps in whenever it arrives |
swap | Fallback font straight away | Swaps in whenever it arrives |
fallback | Invisible for about 100 ms, then fallback | Swaps in only within about 3 seconds |
optional | Invisible for about 100 ms, then fallback | Not used on this page view; may be cached for the next |
Performance
Subsetting and unicode-range
Most of a font's size is its glyphs, and most pages only need a fraction of them. Subsetting removes the glyphs you don't need. A font that covers Latin, Greek and Cyrillic can be split so that an English page downloads only the Latin part. Our sample-digits-subset.woff2 keeps just the digits and a few punctuation marks: 1,400 bytes instead of 4,428.
- Make a subset with
pyftsubsetfrom fontTools:pyftsubset font.ttf --unicodes="U+0000-00FF" --flavor=woff2 --output-file=font-latin.woff2. Google'sglyphhangerand online tools do the same. - Load subsets on demand with the
unicode-rangedescriptor: several@font-facerules share one family name, each with its own file and range, and the browser only downloads files whose range covers characters actually on the page. Google Fonts serves its fonts this way. - Check the licence first. Subsetting modifies the font. Open-source licences such as the SIL Open Font License allow it; some commercial licences don't, and the
OS/2table'sfsTypefield can carry a no-subsetting flag.
Subset too tightly and visitors see missing letters, often as boxes or in a fallback font. Names, addresses and user-generated text bring in accents you didn't plan for, so keep at least the full Latin-1 range for European languages.
One file, many weights
Variable fonts in WOFF2
A variable font holds a whole range of styles in one file. Instead of separate files for light, regular and bold, it stores one set of outlines plus instructions for how each point moves along an axis such as weight (wght) or width (wdth). Variable fonts were added to OpenType in version 1.8 (2016), and WOFF2 compresses them like any other font.
@font-face {
font-family: "99helpers Sample Variable";
src: url("/fonts/sample-variable.woff2") format("woff2");
font-weight: 300 700; /* the range the wght axis covers */
}
h1 { font-weight: 650; } /* any value in the range works */Our sample-variable.woff2 covers weights 300 to 700 in 9,236 bytes, against 8,832 bytes for the separate regular and bold files together, and it can produce every weight in between. Older articles use format("woff2-variations"); current browsers accept plain format("woff2") for variable fonts.
How to
How to open, install and convert a WOFF2 file
- See what it looks like: load it in a page with
@font-face, or open it in a font editor such as FontForge. The live specimen on this page does exactly that with our sample. - Install it on your computer: convert it to TTF or OTF first. Windows and macOS install desktop fonts, not web fonts.
- Convert TTF or OTF to WOFF2: Google's command-line tool
woff2_compress font.ttf, or in Python with fontTools:TTFont('font.ttf'), setfont.flavor = 'woff2', then save. fontTools needs thebrotlipackage for this. - Convert WOFF2 back:
woff2_decompress font.woff2writes the decoded font asfont.ttf, and fontTools does the same when you setflavortoNoneand save. - Inspect it:
ttx -l font.woff2lists the tables;ttx font.woff2dumps everything as XML.
Converting is technically easy, so licences matter more than tools here. A desktop licence often doesn't cover web use, and a web licence often doesn't cover installing the font. Open-source fonts, such as those on Google Fonts, usually allow both.
Rights and risks
Licences, protection and safety
A font file is software, and fonts are licensed like software. Commercial foundries sell separate licences for desktop use, web use (often priced by page views), apps and e-books. Inside the font, the name table usually carries the copyright and licence, and the OS/2 table has an fsType field with embedding permissions such as installable, editable, preview and print, or restricted. Browsers generally don't enforce fsType; the licence is what counts.
WOFF2 doesn't encrypt anything. Anyone who visits a page can save its fonts from the browser's network panel and decompress them. That is why web font licences rely on contracts rather than technical protection.
Fonts are complex binary formats, and font parsers have had serious security bugs over the years. Chrome and Firefox guard against this by checking every web font with the OpenType Sanitizer before handing it to a font engine. A WOFF2 file can't run code, and the risk from a downloaded font is in bugs, not in a feature of the format.
Free downloads
Sample WOFF2 files to download
Free sample WOFF2 fonts for testing @font-face set-ups, CORS and MIME configuration, font loaders and converters. They use 99helpers Sample, an original geometric typeface we drew for these pages, with Latin letters, digits, common punctuation and a few accented letters.
Aa Gg 99
ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789 .,:;!?&@#()
14pxThe quick brown fox jumps over the lazy dog
20pxThe quick brown fox jumps over the lazy dog
32pxThe quick brown fox jumps over the lazy dog
48pxThe quick brown fox jumps over the lazy dog
What's inside sample-regular.woff2
The regular weight, encoded with fontTools. The specimen above is drawn with this exact file.
Header
The
wOF2signature, the TrueType flavor00 01 00 00, and a total length of 4,428 bytes.Table directory
Ten entries in 29 bytes:
OS/2,cmap,glyf,head,hhea,hmtx,loca,maxp,name,post.Transformed glyf and loca
The outlines of 99 glyphs, reorganised into separate streams;
locais rebuilt on decoding.Brotli stream
All tables in one compressed block of 4,351 bytes. Decoded, the font is 11,716 bytes.
Character set
A to Z, a to z, 0 to 9,
.,:;!?&@#()-'"/and space, plus á à â ä é è ê ë í ï ó ô ö ú ü ñ É Ü Ñ.Names and licence
Family
99helpers Sample, styleRegular, weight class 400, with the licence and a link back to this page in thenametable.
All sample WOFF2 files
Regular weight
sample-regular.woff2 · 4.3 KB (4,428 bytes)
99helpers Sample Regular (weight 400) with TrueType outlines: 99 glyphs, Latin letters, digits, punctuation and a few accents.
- Testing `@font-face` and font loading
- Checking a server sends `font/woff2`
Bold weight
sample-bold.woff2 · 4.3 KB (4,404 bytes)
The same family at weight 700, drawn with a heavier stroke rather than faked by the browser. Pair it with the regular file under one
font-familyname.- Testing weight matching across two `@font-face` rules
- Spotting synthetic bold
Variable font, weight 300 to 700
sample-variable.woff2 · 9.0 KB (9,236 bytes)
A variable font with one
wghtaxis from 300 (Light) to 700 (Bold), withfvar,gvar,HVARandSTATtables and five named instances.- Testing variable-font support and `font-weight` ranges
- Animating or fine-tuning weight
Digits-only subset
sample-digits-subset.woff2 · 1.4 KB (1,400 bytes)
The regular font subset to 17 glyphs: 0 to 9, space and
.,:-/. Letters fall back to another font, which makes subsetting andunicode-rangebehaviour easy to see.- Testing `unicode-range` and font fallback
- A tiny valid WOFF2 for upload and parser tests
Ways to test with them
- CORS: serve
sample-regular.woff2from a second domain and load it from a page; withoutAccess-Control-Allow-Originthe browser refuses it and logs a CORS error. - MIME type: request the file with
curl -Iand check forContent-Type: font/woff2. - Weights: declare the regular and bold files under one family and compare
font-weight: 700with the regular file alone, where the browser fakes bold. - Variable fonts: load
sample-variable.woff2withfont-weight: 300 700and step through weights; text should get gradually heavier and wider. - Fallback: use
sample-digits-subset.woff2for a paragraph of mixed text; only the digits should change font.
Free to use for testing, teaching and demos, commercially or not, with no attribution needed. A link back to this page is always appreciated.
Put it online
Have a WOFF2 file? Give it a link.
Drop a WOFF2 file on 99helpers and it gets its own address in seconds. Opening the link downloads the font, since browsers don't display font files on their own, and a page or site you host on 99helpers can load it with @font-face.
Questions
WOFF2 files, frequently asked
What is a WOFF2 file?
A compressed font for websites. It contains a TrueType or OpenType font, compressed with Brotli so pages load faster, and browsers use it through CSS @font-face.
How do I open a WOFF2 file?
Browsers don't show font files on their own. Use it in a web page with @font-face, open it in a font editor such as FontForge, or convert it to TTF to install it on your computer.
Can I install a WOFF2 font on Windows or macOS?
Not directly. Convert it to TTF or OTF first, for example with woff2_decompress, and check that the font's licence allows desktop use.
What's the difference between WOFF and WOFF2?
Both wrap the same kinds of fonts. WOFF compresses each table with zlib; WOFF2 reorganises the outlines and compresses everything with Brotli, which typically makes files about a quarter smaller. Every current browser supports both.
Do I still need WOFF, TTF or EOT fallbacks?
For current browsers, no: WOFF2 alone is supported everywhere. EOT was only ever for Internet Explorer, and WOFF only matters if you still support browsers from before about 2016.
What is the MIME type of WOFF2?
font/woff2, registered in RFC 8081 in 2017. Servers configured before then sometimes send application/font-woff2 or application/octet-stream.
Why does my WOFF2 font work locally but not online?
Usually CORS: the font is on another domain without an Access-Control-Allow-Origin header. Other causes are a wrong relative path in the CSS file or a server sending an HTML error page instead of the font.
Is converting a font to WOFF2 legal?
The format doesn't decide that; the licence does. Open-source fonts generally allow it. Many commercial desktop licences don't cover web use, so check before you convert and upload a font you bought.
Related formats
Other file types explained
Keep going
Related
Sources
- W3C: WOFF File Format 2.0
- W3C: WOFF 2.0 Evaluation Report
- RFC 8081: The font top-level media type
- MDN: @font-face src
- Chrome for Developers: Controlling font performance with font-display
- W3C: CSS Fonts Module Level 4
Last reviewed 27 October 2026.