How to convert ttf or otf to woff online for free
- Step 1Drop your font into the converter — Drag a TTF, OTF, WOFF, or WOFF2 file onto the drop zone, or click to browse. Only one file is processed at a time — for many weights see [the batch script guide](/font-tools/guides/batch-ttf-to-woff-conversion-script). The format is sniffed from the magic bytes, so renaming the extension doesn't matter.
- Step 2Let it decode the input to sfnt if needed — TTF and OTF are already sfnt and pass straight through. A WOFF2 input is Brotli-decompressed back to sfnt first; a WOFF input is inflated and reflattened. Either way the tool ends up with a flat sfnt buffer before it starts wrapping.
- Step 3Press Process — no options to configure — There are no settings, presets, or checkboxes for this tool — the conversion is deterministic. Click Process and the wrapper runs in milliseconds for typical Latin fonts (a few hundred ms for large CJK fonts).
- Step 4Each table is zlib-deflated independently — The encoder walks the sfnt table directory and `pako.deflate`s each table's bytes. For each table it keeps whichever is smaller — the deflated stream or the original — so tiny or already-compressed tables are stored raw rather than inflated.
- Step 5Check the metrics panel — The result shows Source format, WOFF size, Original, Output, and a Saved % badge when the WOFF is smaller than the source. For an already-compressed source (a WOFF2 input) the WOFF can be larger — no Saved badge appears in that case, which is expected.
- Step 6Download and wire it into @font-face — Download the `.woff` and add it to your `src` list with `format("woff")`. Put WOFF2 first and WOFF second so modern browsers stop at the smaller file — the [@font-face Generator](/font-tools/font-face-generator) emits the full multi-format block for you.
Input formats this converter accepts
Format is detected from the first 4 magic bytes, not the file extension. A mislabeled file still routes correctly.
| Input | Magic bytes | What happens | Result |
|---|---|---|---|
| TTF (TrueType) | 0x00010000 (or Apple true) | Already sfnt — used directly | WOFF with flavor = 0x00010000 |
| OTF (CFF / PostScript) | OTTO (0x4F54544F) | Already sfnt — used directly | WOFF with flavor = OTTO (stays CFF) |
| WOFF2 | wOF2 (0x774F4632) | Brotli-decoded to sfnt first, then re-wrapped | WOFF — usually larger than the WOFF2 input (zlib vs Brotli) |
| WOFF 1.0 | wOFF (0x774F4646) | Inflated to sfnt, then re-wrapped | WOFF — effectively a re-pack; size ≈ input |
| TTC (collection) | ttcf (0x74746366) | Rejected — multi-face containers aren't unwrapped | Error: Unsupported font format: ttc |
What lands in the WOFF 1.0 header
The exact fields this tool writes, per the W3C WOFF specification. All multi-byte fields are big-endian.
| Field | Value written | Notes |
|---|---|---|
signature | 0x774F4646 (wOFF) | First 4 bytes — identifies the file as WOFF 1.0 |
flavor | Copied from the source sfnt version | 0x00010000 (TTF) or OTTO (CFF/OTF) — preserved, never coerced |
numTables | Source table count | Same tables as the input, in the source directory order |
totalSfntSize | Reconstructed sfnt size | 12-byte sfnt header + 16×N directory + padded original lengths |
majorVersion / minorVersion | 1 / 0 | WOFF file version |
metaOffset / metaLength / metaOrigLength | 0 / 0 / 0 | Optional XML metadata block is left empty by this tool |
privOffset / privLength | 0 / 0 | Optional private block is left empty |
Per-tier per-file size limit
Limits apply to the input font file. There is no separate output cap.
| Tier | Max input file | Where conversion runs |
|---|---|---|
| Free | 5 MB | In the browser tab (JS / pako) |
| Pro | 50 MB | Auto-routes to the local runner when paired, else in-browser |
| Developer | 1 GB | Auto-routes to the local runner when paired, else in-browser |
Cookbook
Real conversions with the before/after sizes you'll see in the metrics panel. Numbers are representative — your fonts vary by glyph count and outline complexity.
Plain TTF to WOFF — the common case
ExampleA static Latin TrueType font. Each table is zlib-deflated; the WOFF lands roughly 35-45% smaller than the raw TTF. The Saved badge shows the reduction.
Input: Inter-Regular.ttf (309 KB, magic 0x00010000) Process → wrap as WOFF 1.0, pako.deflate each table Result panel: Source format: TTF WOFF size: 188.4 KB Original: 309 KB Output: 188 KB Saved: 39% Download: Inter-Regular.woff (font/woff)
OTF (CFF) to WOFF keeps PostScript outlines
ExampleAn OpenType/CFF font. The flavor field copies OTTO, so the wrapped font is still CFF-flavoured — the browser renders the same PostScript outlines. WOFF2-to-TTF would surface this as an .otf; here it stays inside the WOFF wrapper.
Input: SourceSerif4.otf (magic 'OTTO') Process → WOFF with flavor = OTTO Hexdump of output, bytes 0-7: 77 4F 46 46 4F 54 54 4F w O F F O T T O ← signature, then preserved flavor
Re-targeting a WOFF2 you already shipped
ExampleYou only have the production WOFF2 and need a WOFF fallback. Drop the WOFF2 in: it is Brotli-decoded to sfnt, then re-wrapped as WOFF. Expect the WOFF to be larger than the WOFF2 (zlib can't match Brotli) — there's no Saved badge, by design.
Input: brand.woff2 (84 KB, magic 'wOF2') Process → Brotli-decode to sfnt → wrap as WOFF 1.0 Result panel: Source format: WOFF2 WOFF size: 121.6 KB ← larger than the 84 KB input (no Saved badge — output exceeds input) Ship both: WOFF2 first (84 KB), WOFF fallback (122 KB).
The @font-face block to drop the WOFF into
ExampleOrder matters: WOFF2 first means modern browsers download the smaller file and never touch the WOFF. The format() hint lets each browser skip what it can't parse.
@font-face {
font-family: "Inter";
font-weight: 400;
font-style: normal;
font-display: swap;
src: url("/fonts/inter.woff2") format("woff2"),
url("/fonts/inter.woff") format("woff");
}Mislabeled extension still converts
ExampleA designer handed you a file named .otf that is actually TrueType-flavoured. Detection reads the magic bytes, not the name, so it converts correctly — and the metrics panel reports the true source format.
Input: DisplayFont.otf (but magic = 0x00010000, i.e. TrueType) Process → detected as TTF, wrapped with flavor 0x00010000 Result panel: Source format: TTF ← reports the real format, not 'OTF' WOFF size: 94.1 KB
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.
Source is a WOFF2 — output WOFF is bigger
ExpectedWOFF2 uses Brotli plus a glyf/loca transform; WOFF 1.0 uses per-table zlib with no transform. Re-wrapping a WOFF2 as WOFF inflates it back to sfnt and recompresses with the weaker algorithm, so the WOFF is typically 20-40% larger than the WOFF2 you started with. No Saved badge appears — that's correct, not a bug. If you have the original TTF/OTF, convert from that instead for a cleaner result.
A table won't compress smaller than the original
By designFor each table the encoder keeps whichever is smaller: the zlib stream or the raw bytes. Tiny tables (head, hhea, maxp) and already-dense ones often don't shrink, so they're stored uncompressed with compLength equal to origLength. This is exactly what the WOFF spec allows and is normal — it prevents the wrapper from ever making an individual table bigger.
You upload a TrueType Collection (.ttc)
Unsupported formatThe ttcf magic is detected, but this tool only wraps a single sfnt face. The conversion throws Unsupported font format: ttc. A collection bundles several faces sharing tables — split it into individual TTF/OTF files on the desktop (FontForge or fonttools ttx) first, then convert each face separately.
File exceeds your tier's per-file limit
413-style rejectFree tier caps the input at 5 MB, Pro at 50 MB, Developer at 1 GB. A larger file is rejected before processing with File "…" exceeds the <tier> tier per-job limit. Most single-weight Latin fonts are well under 1 MB; CJK fonts can be 5-20 MB and need Pro or Developer. On paid tiers a paired local runner handles the heavy lifting.
The uploaded file isn't a font at all
Invalid inputIf the first 4 bytes don't match any known font magic, detection returns unknown and fileToSfntBuffer throws Unsupported font format: unknown. A renamed ZIP, PDF, or image will trip this. Verify what you actually have with the Font Format Identifier, which reads only the first bytes.
Hinting, kerning, and OpenType features after conversion
PreservedWOFF is a lossless wrapper. Every table from the source — cmap, outlines (glyf or CFF ), GSUB, GPOS, kern, the hinting tables (cvt , fpgm, prep), even variable-font tables (fvar, gvar) — is carried through byte-for-byte. Only the framing and per-table compression change; rendering is identical to the source.
Output ships but the browser shows a fallback font
Wiring issueThe WOFF itself is valid; the usual culprit is the @font-face src. Missing format("woff") hint, a wrong path, or a CSP font-src that blocks the file all cause silent fallback. Confirm the file loads in the Network tab, and check the format hint matches. The @font-face Generator produces a known-good block.
Round-tripping WOFF back to TTF
SupportedWOFF inflates losslessly back to the same sfnt tables. This tool itself accepts a WOFF as input. For a TTF/OTF download specifically, the WOFF2 to TTF tool is the dedicated unwrapper — convert your WOFF to WOFF2 first if you want a one-click TTF out, or just re-use your original source font.
Variable font converts but won't vary in old browsers
By designA variable TTF wraps to WOFF with fvar/gvar intact, and font-variation-settings still works in modern browsers. But the browsers that need WOFF (IE11) never supported variable fonts at all, so they render the default instance only. That's a browser limitation, not a conversion loss — the data is all there for capable engines.
Frequently asked questions
When should I prefer WOFF over WOFF2?
Almost never as your only format in 2026 — WOFF2 has 99%+ support. WOFF earns its place as a fallback when you must support IE11 (some enterprise intranets) or embedded WebKit views without Brotli. The pattern is always WOFF2 first, WOFF second in the src list, so 99% of users get the smaller file. See WOFF vs WOFF2: when to ship both.
What file size difference should I expect?
From a raw TTF, WOFF is typically 35-45% smaller (per-table zlib). It's usually 20-40% larger than the equivalent WOFF2 (Brotli plus the glyf transform wins). Converting a WOFF2 input back to WOFF inflates it, so that output is larger than the WOFF2 you fed in — convert from the original TTF/OTF for the best result.
Does WOFF preserve OpenType features and hinting?
Yes — it's a lossless wrapper. Every table (cmap, outlines, GSUB, GPOS, kern, and the hinting tables cvt , fpgm, prep) is carried through byte-for-byte. Only the wire format and per-table compression change. If you want to drop hinting to save bytes, that's the separate Hinting Stripper tool — this converter never strips anything.
Can I convert OTF (CFF) fonts, not just TTF?
Yes. OTF with PostScript/CFF outlines wraps to WOFF just like TrueType — the flavor field copies OTTO, so the result stays CFF-flavoured and renders identically. The metrics panel reports the real source format detected from the magic bytes.
Does the file extension matter?
No. Detection reads the first 4 magic bytes, not the extension. A .otf that is really TrueType, or a .ttf that is really CFF, converts correctly — and the result panel reports the true format. To inspect what a file actually is, use the Font Format Identifier.
Are there any options or settings?
No. The WOFF wrap is deterministic — same input always produces the same output bytes — so there's nothing to configure. You upload one file and press Process. If you need to subset, strip hinting, or freeze variable axes before wrapping, do that with the dedicated tools first, then convert.
Is the metadata block included?
No. WOFF can carry an optional XML metadata block (licence, vendor) and a private block, but this tool leaves both empty (metaOffset/metaLength/privOffset/privLength are all 0). Those blocks don't affect rendering. The licence and name information lives in the font's own name table, which is preserved — inspect it with the Font Metadata Extractor.
Is my font uploaded anywhere?
No. The font is read with the File API and wrapped in JavaScript inside your browser tab; nothing is transmitted. The result panel shows a 0 bytes uploaded badge. On Pro and Developer tiers the work can route to a local runner on your own machine — still no upload to JAD servers.
What's the maximum file size?
Free tier accepts up to 5 MB, Pro up to 50 MB, Developer up to 1 GB per file. Single-weight Latin fonts are usually under 1 MB; large CJK fonts can be several MB and may need a paid tier. The limit applies to the input file.
Can I convert many fonts at once?
Not in the browser UI — it processes one file at a time. For a whole design system, script it: use pako in Node, or POST each font to the local runner endpoint http://127.0.0.1:9789/v1/tools/ttf-to-woff/run. The batch conversion guide walks both paths.
Should I also generate WOFF2?
Yes, for almost every site. WOFF2 is the primary format; WOFF is the fallback. Run the same font through TTF to WOFF2, then list both in @font-face with WOFF2 first. The @font-face Generator can emit the multi-format block with checkboxes for WOFF2, WOFF, and TTF.
Why does my WOFF have the same tables in a different order than expected?
On the way in, this tool keeps the source sfnt's table directory order when wrapping (it does not re-sort). When it decodes a WOFF back to sfnt, it sorts tables by tag as the sfnt format requires. Either way the table data is identical — order in the directory doesn't change rendering, only byte layout.
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.