How to subset a font online to shrink woff2 by 60–90%
- Step 1Confirm the source font has what you need — Run the [Character Coverage Map](/font-tools/character-coverage-map) first. It scores your font against 346 real Unicode blocks, so you can see whether the source even contains the Latin-Ext or Cyrillic glyphs you're about to subset to. Subsetting to a charset the font doesn't cover throws a `Subset would be empty` error — better to catch that here.
- Step 2Drop your font onto the tool — Any TTF, OTF, WOFF, or WOFF2 up to the tier limit (5 MB free, 50 MB Pro). opentype.js parses the full glyph table; WOFF2 and WOFF wrappers are decompressed to a flat sfnt first so the parser sees raw `glyf`/`CFF ` outlines.
- Step 3Pick a charset from the dropdown — The control is a single **Charset** select with six values — there is no free-text box here (that lives in the [Character Whitelist Builder](/font-tools/character-whitelist-builder)). Latin Basic + Latin-1 covers English plus most Western-European accents. Latin-Ext adds Polish, Czech, Turkish. Vietnamese here means Latin-Ext ranges plus the đồng sign `U+20AB`.
- Step 4Process and read the metrics — The tool keeps every glyph whose Unicode codepoint (or any of its `unicodes` aliases) is in the charset, plus `.notdef`. The result panel reports glyphs in source, glyphs kept, source KB, subset KB, and a note that kerning and OT layout were dropped.
- Step 5Download the subset TTF — Output is named `yourfont.<charset>.ttf` — for example `Inter.latin.ttf`. This is an intermediate, not the final deliverable: the raw TTF is usually larger than the original WOFF2 you may have started with because it isn't compressed yet.
- Step 6Compress to WOFF2 — Send the subset TTF through [TTF→WOFF2](/font-tools/ttf-to-woff2). This brotli step is where the headline 60–90% reduction lands. Then wire it into a [@font-face block](/font-tools/font-face-generator) with the matching `unicode-range`.
The six charset presets and what they actually contain
Exact codepoint ranges from the tool's UNICODE_SUBSETS table. These are the only presets in the dropdown — there is no custom-text option on this tool.
| Dropdown value | Codepoint ranges | Covers | Approx. glyph count |
|---|---|---|---|
| Latin (default) | U+0020–007E, U+00A0–00FF | English + Western European (French, German, Spanish, Italian, Portuguese accents) | ~190 codepoints |
| Latin-Ext | U+0100–024F, U+1E00–1EFF | Latin Extended-A/B + Additional — Polish, Czech, Turkish, Welsh, Vietnamese base | ~590 codepoints |
| Cyrillic | U+0400–04FF, U+0500–052F | Russian, Ukrainian, Bulgarian, Serbian + Cyrillic Supplement | ~304 codepoints |
| Greek | U+0370–03FF, U+1F00–1FFF | Modern + polytonic Greek | ~400 codepoints |
| Vietnamese | U+0100–024F, U+1E00–1EFF, U+20AB | Latin-Ext ranges + the đồng currency sign — note this is NOT a Vietnamese-only block | ~590 codepoints |
| Symbols | U+2000–206F, U+2070–209F, U+20A0–20CF | General punctuation, super/subscripts, currency symbols | ~208 codepoints |
What subsetting keeps vs drops (opentype.js writer)
Grounded in subsetByCodepoints in font-processor.ts. The writer rebuilds outlines into a fresh single-style font; everything not in that rebuild is gone.
| Font feature | After subset | Workaround if you need it |
|---|---|---|
Glyph outlines (glyf / converted CFF ) | Preserved for the kept codepoints + .notdef | — |
cmap for kept glyphs | Preserved — rebuilt from the kept set | — |
Kerning (kern, GPOS) | Dropped — writer does not round-trip layout tables | Use a layout-preserving engine (pyftsubset, hb-subset) — see the build-pipeline guide |
Ligatures / alternates (GSUB) | Dropped | Same — pyftsubset --layout-features='*' |
Variable axes (fvar/gvar/avar) | Dropped — output is single-style | Freeze an instance first, then subset |
Colour layers (COLR/CPAL/sbix) | Dropped → monochrome outlines | If you want colour, don't subset; if mono is the goal it's automatic |
| Output container | TTF only (font/ttf) | Run TTF→WOFF2 for the web deliverable |
Cookbook
Real before/after numbers and the two-step pattern that produces the headline shrink. Sizes are typical for a Latin sans-serif; your font will vary.
Inter Latin subset — the canonical English-only case
ExampleA full Inter Regular TTF carries ~2,500 glyphs across many scripts. Subset to the default Latin charset and compress, and you keep only the ~190 codepoints an English marketing page renders.
Source: Inter-Regular.ttf 312 KB (~2,500 glyphs)
Step 1: Font Subsetter, Charset=Latin
→ Inter-Regular.latin.ttf 58 KB (~190 glyphs kept + .notdef)
Step 2: TTF→WOFF2
→ Inter-Regular.latin.woff2 19 KB
Net: 312 KB → 19 KB (94% smaller end-to-end)
Note panel: "Kerning + OT layout features dropped; outlines preserved"Re-subsetting a WOFF2 you already shipped
ExampleYou inherited a 90 KB WOFF2 with Cyrillic baked in but your site is English-only. The tool decompresses the WOFF2 via wawoff2, then subsets — no need to find the original TTF.
Input: brand.woff2 90 KB (Latin + Cyrillic)
Font Subsetter auto-decompresses WOFF2 → sfnt, Charset=Latin
→ brand.latin.ttf 41 KB (TTF, uncompressed)
TTF→WOFF2 → brand.latin.woff2 16 KB
The intermediate TTF (41 KB) is BIGGER than the input WOFF2 (90 KB
was compressed). That's expected — compression happens in step 2.Latin-Ext for a multi-locale Western/Central European site
ExampleEnglish + Polish + Czech + Turkish copy needs Latin Extended-A/B. Pick Latin-Ext rather than Latin so ł, ě, ş and friends don't render as tofu.
Source: Roboto.ttf 168 KB
Charset=Latin-Ext (U+0100–024F, U+1E00–1EFF)
→ Roboto.latin-ext.ttf 72 KB (~590 glyphs kept)
TTF→WOFF2 → Roboto.latin-ext.woff2 27 KB
Using plain Latin here would drop ł → page shows □ where Polish text is.Empty-subset error — charset the font doesn't have
ExampleSubset a Latin-only font to Cyrillic and the tool refuses rather than emitting a broken file. This is the safety check that fires when zero glyphs match.
Source: OpenSans-Latin-only.ttf
Charset=Cyrillic
Error:
Subset would be empty — no glyphs in this font match the
requested charset (cyrillic). Run the Character Coverage Map
tool first to confirm coverage.
Fix: run /font-tools/character-coverage-map → confirm the font
has Cyrillic before choosing that preset.When you need exact glyphs, not a preset — use the sibling tool
ExampleThe Font Subsetter has only the six presets. To keep an exact set like a logo's letters or a numeric ticker, the Character Whitelist Builder takes a pasted string.
Goal: keep only the characters in "JAD APPS 0123456789" Font Subsetter: no free-text option — presets only. Use instead → /font-tools/character-whitelist-builder Paste: JAD APPS 0123456789 → yourfont.subset.ttf (12 unique codepoints + .notdef) → then TTF→WOFF2 for a sub-2 KB display font.
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.
Output is a TTF, not the WOFF2 you expected
By designThe subsetter always writes font/ttf. The headline 60–90% shrink only lands after you run the result through TTF→WOFF2. If you compare the raw subset TTF against an already-compressed WOFF2 source, the TTF can look larger — that's the uncompressed intermediate, not a failure.
Kerning looks wrong / ligatures stopped joining
Expectedopentype.js's writer does not round-trip kern/GPOS/GSUB, so all kerning, ligatures and stylistic sets are dropped during subset. The metrics panel says so explicitly. For text that needs layout features intact, use a layout-preserving engine per the build-pipeline guide, then verify with the Kerning Pair Auditor.
Variable font lost its weight slider
ExpectedThe output is a single static style — fvar/gvar axis tables are not preserved. The new font is built from unitsPerEm, ascender/descender and the kept glyph outlines only. If you need a specific weight, freeze an instance first, then subset that static file.
Colour emoji became black-and-white
ExpectedCOLR/CPAL/sbix colour layers are not carried through the rebuild, so a colour font collapses to monochrome outlines. If your goal was a mono font this is convenient; if you wanted colour preserved, subsetting is the wrong tool — leave colour fonts untouched.
Subset would be empty
errorThrown when none of the font's glyphs fall inside the chosen charset (e.g. Cyrillic preset on a Latin-only font). The error names the charset and points you to the Coverage Map. Pick a preset the font actually covers, or use the Character Whitelist Builder with characters you know exist.
CFF / OTF font fails in the writer
erroropentype.js can read CFF (PostScript) outlines but its writer prefers TrueType outlines and throws on some CFF/CFF2 or non-standard tables: Subsetting failed in opentype.js's font writer. Convert to TTF on the desktop first, or use a smaller charset via the Whitelist Builder. For CFF at scale, the pipeline engines handle CFF natively.
Vietnamese preset didn't add the tone-mark block you expected
By designThe Vietnamese option maps to Latin Extended-A/B (U+0100–024F, U+1E00–1EFF) plus the đồng sign U+20AB — the same outlines Latin-Ext uses, since Vietnamese is written with Latin + diacritics. It is not a separate CJK-style block. For most fonts this is exactly the right coverage; just don't expect a distinct Unicode 'Vietnamese' range.
File over the tier limit won't upload
413Free tier caps font files at 5 MB; Pro at 50 MB. A 12 MB CJK TTF is rejected on free with an over-limit message before processing. Either upgrade, or convert/strip the source down first (a hinting-stripped or colour-stripped file is smaller going in).
The .notdef box still renders for missing characters
PreservedGlyph 0 (.notdef) is always kept, so any character your page uses that fell outside the subset shows the font's missing-glyph box rather than disappearing. That's the visible signal your charset was too narrow — widen the preset or move to the Whitelist Builder.
Frequently asked questions
How small can my font actually get?
End-to-end (subset + WOFF2) a typical Latin sans drops 60–95%: a ~300 KB full TTF often lands near 15–25 KB as a Latin WOFF2. The exact figure depends on how many glyphs the preset keeps and the font's outline complexity. Remember the subsetter alone outputs an uncompressed TTF — the big number comes after TTF→WOFF2.
Are kerning and ligatures preserved?
No. Glyph outlines and the cmap are preserved, but the opentype.js writer drops kern, GPOS, GSUB, variable axes and colour tables. The metrics panel states this directly. For typography that depends on those features, subset with a layout-preserving engine (see the build-pipeline guide) and confirm with the Kerning Pair Auditor.
Can I define a custom set of characters to keep?
Not on this tool — the Font Subsetter exposes only a six-option charset dropdown (Latin, Latin-Ext, Cyrillic, Greek, Vietnamese, Symbols). For an exact glyph list (logo letters, a fixed numeric set), use the Character Whitelist Builder, which takes a pasted string and keeps exactly those codepoints plus .notdef.
What output format does it produce?
Always a .ttf named yourfont.<charset>.ttf. It is an intermediate; for the web you run it through TTF→WOFF2. The tool does not emit WOFF or WOFF2 directly.
What input formats can I upload?
TTF, OTF, WOFF and WOFF2. WOFF2 is decompressed with wawoff2 and WOFF via a zlib path before parsing, so you can re-subset an already-shipped webfont without hunting for the original source file.
Can I subset variable fonts?
You can run them through, but the result is a single static style — fvar/gvar axes are dropped. For a usable result, freeze the instance you want first, then subset that static TTF. For true axis-preserving variable subsetting you need desktop pyftsubset/hb-subset.
What's the difference vs the Latin Filter tool?
Latin Filter is a one-click shortcut that always subsets to the Latin charset — same engine, no dropdown. The Font Subsetter is the general version where you pick from six presets. Use Latin Filter when you know it's English-only; use the Subsetter when you need Cyrillic, Greek, Latin-Ext, etc.
Why is the .notdef glyph always kept?
Glyph 0 is the missing-glyph box every font requires. Keeping it means a character outside your subset renders a visible box instead of breaking the shaper — and gives you a clear signal your charset was too narrow.
What if I pick a charset the font doesn't have?
The tool refuses with Subset would be empty — no glyphs in this font match the requested charset, rather than producing a broken file. Run the Character Coverage Map first; it scores the font against 346 Unicode blocks so you know which presets are valid.
How big a font can I subset on the free tier?
Up to 5 MB per file on free, one file at a time. Pro raises this to 50 MB and 20 files per batch. The free glyph ceiling is 1,000 kept glyphs; Pro is 65,536; Developer is unlimited. Most Latin/Cyrillic/Greek presets stay well under 1,000.
Does the font leave my device?
No. opentype.js and the WOFF2 codec run in your browser; the file is processed locally. Only an anonymous processed-count is recorded for signed-in dashboard stats — never the font bytes.
How do I wire the subset into my CSS?
After subsetting and compressing, generate the rule with the Font-Face Generator and add a matching unicode-range so the browser only fetches it when those characters appear. For lazy multi-language loading, see Font Subsetting vs unicode-range Strategies.
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.