How to ttf vs woff vs woff2: which web font format to ship
- Step 1Identify what you already have — Read the first four bytes: `00 01 00 00` is TrueType TTF, `OTTO` is OpenType/CFF, `wOFF` is WOFF 1.0, `wOF2` is WOFF2. The [Font Format Identifier](/font-tools/font-format-identifier) shows this plus the wrapped flavor and decompressed size in one click.
- Step 2Decide your support floor — If your analytics show no meaningful IE11 or pre-2016 traffic, you only need WOFF2. If you must support IE11 or old embedded WebKit views without Brotli, you also need WOFF 1.0. TTF on the web is essentially never the right answer except as a desktop install.
- Step 3Convert to WOFF2 — Run your TTF/OTF through [TTF to WOFF2](/font-tools/ttf-to-woff2). It applies the same Brotli + `glyf`/`loca` transform the spec defines, fully in your browser. If you also need a WOFF fallback, produce it with [TTF to WOFF](/font-tools/ttf-to-woff).
- Step 4Order the src list correctly — Browsers use the first format in the `src` list they understand, so list WOFF2 first, WOFF (if any) second. Add `format("woff2")` / `format("woff")` hints so a browser can skip a download it couldn't parse.
- Step 5Subset before you compress — Compression and subsetting are different levers. WOFF2 shrinks the bytes you keep; subsetting reduces how many glyphs you keep. For the smallest result, subset first with the [Font Subsetter](/font-tools/font-subsetter), then convert to WOFF2.
- Step 6Verify the shipped bytes — After build, confirm the served file's first bytes are `wOF2` and that the `Content-Type` is `font/woff2`. A misconfigured server that returns `text/plain` can stop some setups from caching the font correctly.
TTF vs OTF vs WOFF vs WOFF2
All four wrap the same SFNT tables. Magic bytes and compression are grounded in the format detector and converter implementation.
| Property | TTF | OTF | WOFF 1.0 | WOFF 2.0 |
|---|---|---|---|---|
| Magic (first 4 bytes) | 0x00010000 | OTTO | wOFF | wOF2 |
| Outline type | TrueType (glyf) | CFF (PostScript) | Either (wraps SFNT) | Either (wraps SFNT) |
| Compression | None | None | zlib/DEFLATE per table | Brotli over whole font |
| Table transform | — | — | No | Yes (glyf/loca) |
| Typical web size | 100% (baseline) | ~100% | ~55–60% of TTF | ~35–50% of TTF |
| Browser support | Desktop / legacy | Desktop / legacy | IE9+ and all modern | All modern (no IE) |
| Right for web? | No | No | Legacy fallback only | Yes — default |
Compression mechanism, in detail
Why WOFF2 wins: it doesn't just compress harder, it reshapes the glyph data first so the compressor has more redundancy to exploit.
| Aspect | WOFF 1.0 | WOFF 2.0 |
|---|---|---|
| Compressor | zlib/DEFLATE (RFC 1951) | Brotli (RFC 7932), large built-in dictionary |
| Scope | Each table compressed separately | All tables in one shared stream |
| Glyph preprocessing | None — raw glyf/loca | glyf/loca transform splits coordinate streams |
| Table directory | Explicit offset + comp/orig length per table | No offsets; sizes as UIntBase128, known-tag flags byte |
| Net effect vs the other | ~25–35% larger than WOFF2 | ~25–35% smaller than WOFF |
Which format(s) to ship
A decision matrix for 2026. WOFF2-only is the default; everything else is a deliberate compatibility trade.
| Audience / constraint | Ship | Why |
|---|---|---|
| Modern public website | WOFF2 only | >99% support; smallest payload; one request |
| Must support IE11 / old WebKit | WOFF2 + WOFF | WOFF2 first, WOFF fallback for the no-Brotli engines |
| Email (Outlook desktop) | Neither — system fallback | Word engine strips every @font-face; design the fallback |
| Desktop app install / editing | TTF or OTF | WOFF/WOFF2 are web wrappers, not install formats |
| Self-hosting Google Fonts | WOFF2 only | Google's own served files are WOFF2 for modern UAs |
Cookbook
Copy-paste @font-face patterns and the one-liners to verify which format you actually have. Order matters: browsers take the first format they understand.
Modern default — WOFF2 only
ExampleFor >99% of 2026 traffic this is all you need. The 0.x% on truly ancient engines fall back to the system font.
@font-face {
font-family: "Brand";
font-weight: 400;
font-display: swap;
src: url("/fonts/brand.woff2") format("woff2");
}WOFF2 first, WOFF fallback
ExampleOnly when analytics show real IE11 / no-Brotli traffic. List WOFF2 first so modern browsers never download the WOFF.
@font-face {
font-family: "Brand";
font-weight: 400;
font-display: swap;
src: url("/fonts/brand.woff2") format("woff2"),
url("/fonts/brand.woff") format("woff");
}Identify any font from its first bytes
ExampleThe wrapper is unambiguous at byte 0 — no parsing needed. The Font Format Identifier does this for you and also shows the wrapped flavor.
$ xxd -l 4 a.woff2 → 774f 4632 'wOF2' (WOFF 2.0) $ xxd -l 4 b.woff → 774f 4646 'wOFF' (WOFF 1.0) $ xxd -l 4 c.ttf → 0001 0000 .... (TrueType SFNT) $ xxd -l 4 d.otf → 4f54 544f 'OTTO' (OpenType/CFF SFNT)
Convert TTF/OTF → WOFF2 for production
ExampleWOFF2 is the shipping format. Drop the source into the converter; the SFNT is wrapped with Brotli + the glyf transform.
Drop name.ttf (or name.otf) → /font-tools/ttf-to-woff2 → download name.woff2 # Need a legacy fallback too? Also run /font-tools/ttf-to-woff
Self-hosting Google Fonts — keep it WOFF2
ExampleGoogle's CSS serves WOFF2 to modern UAs already. Generate a privacy-friendly self-hosted block, then point it at your own WOFF2 files.
Use /font-tools/google-fonts-css-generator to produce the @font-face, then host the .woff2 files on your own domain (no third-party request).
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.
Wrong src order — WOFF listed before WOFF2
Wasted bytesBrowsers use the first src entry whose format() they support. If you list WOFF before WOFF2, a modern browser downloads the larger WOFF and never reaches the WOFF2. Always order WOFF2 first, WOFF second.
Missing format() hint on the src URL
SuboptimalWithout format("woff2") the browser may have to start a download to discover it can't use a font. The hint lets it skip formats it doesn't support before fetching. It does not validate the bytes — it only advertises the intended type.
Server returns text/plain for a .woff2
MisconfigurationA correctly built WOFF2 served with the wrong Content-Type can break caching or some strict loaders. Configure the server to send font/woff2. The file's first bytes are still wOF2 regardless of the header — verify with the Font Format Identifier.
Shipping TTF to the web 'because it works everywhere'
Avoidable bloatTTF renders in every browser but is uncompressed — typically ~2× the WOFF2 size. There is no compatibility upside on the modern web (WOFF2 + system fallback covers the same audience), only a bandwidth cost. Convert to WOFF2.
An .otf and a .ttf of the 'same' font differ in size
By designOTF uses CFF (PostScript) outlines; TTF uses quadratic glyf outlines — they're genuinely different curve encodings, so raw sizes differ before any compression. After WOFF2 wrapping both shrink substantially; ship whichever source you have.
WOFF2 won't render in Internet Explorer 11
Expected — no Brotli in IEIE11 cannot decode WOFF2 and simply ignores that src entry. If IE11 support genuinely matters, add a WOFF 1.0 fallback in the same @font-face; otherwise IE11 users get the system font.
Email client strips @font-face entirely
Format-independentOutlook on Windows renders through Microsoft Word's HTML engine, which removes every @font-face rule regardless of TTF/WOFF/WOFF2. No format choice fixes this — design the fallback stack so the email still reads correctly.
CJK font: WOFF2 still big after conversion
By design — subset insteadCJK glyph data is information-dense, so WOFF2 saves ~25–35% (vs ~60% on Latin). The format isn't the lever here — subsetting to the characters you use is. Use the Font Subsetter, then convert to WOFF2.
Frequently asked questions
Is WOFF2 a different font format from TTF?
No. WOFF2 is a compression and packaging wrapper around the same SFNT container TTF and OTF use. After decompression and reversing the glyf/loca transform, the browser holds an in-memory SFNT equivalent to the original font — the glyph outlines and tables are unchanged.
How much smaller is WOFF2 than TTF and WOFF?
Roughly 50–65% smaller than the uncompressed TTF, and ~25–35% smaller than WOFF 1.0, for the same glyph data. The exact figure depends on the font: Latin text faces compress best; CJK and icon fonts (dense glyph data) compress less. Always measure your own font.
Why does WOFF2 compress better than WOFF or zip?
Two reasons. First, it uses Brotli (with a large built-in dictionary) instead of zlib/DEFLATE. Second, it applies a glyf/loca transform that reorganises glyph coordinate data into a form with more redundancy before compressing — something generic zip and WOFF 1.0 don't do.
Should I still ship a WOFF fallback in 2026?
Almost never. WOFF2 support is >99%, and the missing fraction is largely Internet Explorer 11 and discontinued mobile browsers. Ship WOFF2-only unless your analytics show meaningful IE11/no-Brotli traffic — in which case list WOFF2 first and WOFF second.
Is TTF ever the right web format?
Not for delivery. TTF is fine as a desktop install or an editing source, but on the web it's an uncompressed payload roughly double the WOFF2 size with no compatibility benefit over WOFF2 + a system fallback. Convert it.
Does converting between formats change rendering?
No. TTF→WOFF2, WOFF→WOFF2, and WOFF2→TTF all preserve the SFNT table data byte-equivalently. Glyphs, metrics, kerning, ligatures, and hinting are identical — only the optional metadata/private blocks can be dropped by a converter.
What's the difference between TTF and OTF?
Outline type. TTF uses TrueType quadratic outlines in a glyf table (magic 0x00010000); OTF uses CFF/PostScript cubic outlines (magic OTTO). Both are SFNT containers and both wrap into WOFF2 identically. The converter detects and handles either.
How do I identify which format a file is?
Read the first four bytes: 00 01 00 00 = TTF, OTTO = OTF, wOFF = WOFF, wOF2 = WOFF2. Use xxd -l 4 file or the Font Format Identifier, which also reports the wrapped flavor and decompressed size.
Can I convert WOFF2 back to TTF?
Yes — use WOFF2 to TTF. It decompresses the WOFF2 to its SFNT and writes a TTF (or OTF if the flavor is CFF). This is the round-trip you need for desktop editing in a font app.
Does WOFF2 work with variable fonts?
Fully. WOFF2 wraps the SFNT including fvar/gvar/STAT, so a variable font keeps its axes after conversion. The glyf/loca transform applies to outlines; variation tables ride along in the Brotli stream.
What about the deep internals of these wrappers?
For the byte-level header layouts, magic numbers, and the glyf/loca transform, see WOFF & WOFF2 internals explained. This page is the practical chooser; that one is the spec reference.
Do I need EOT or SVG fonts anymore?
No. EOT was IE-only and is dead; the SVG font format was deprecated and removed from browsers. The only formats worth shipping in 2026 are WOFF2 (default) and, rarely, WOFF as a legacy fallback. TTF/OTF stay on the desktop.
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.