How to complete reference: font format magic bytes
- Step 1Read bytes 0–3 — Open the file, read the first 4 bytes, and form the big-endian uint32. That value is the magic. The JAD tool does exactly this via the File API.
- Step 2Look it up — Match against the master table below. `0x00010000`/`true` → TTF; `OTTO` → OTF; `wOFF` → WOFF; `wOF2` → WOFF2; `ttcf` → TTC. Anything else → unknown.
- Step 3Decode the follow-up bytes — After the magic each format differs. sfnt: numTables is a uint16 at offset 4. WOFF/WOFF2: the wrapped sfnt flavor is a uint32 at offset 4, and a size field sits at offset 16.
- Step 4Interpret 'unknown' magics — If the magic isn't a font value, its hex usually reveals the real file type: `0x504B0304` is a ZIP, `0x89504E47` is a PNG, `0x3C` (`<`) is often an HTML error page saved with a font name.
- Step 5Cross-reference the spec — Each format's spec defines the magic first: the OpenType spec for sfnt, the W3C WOFF and WOFF2 recommendations for the wrappers. The magic is the entry point for deeper investigation.
- Step 6Verify against the tool — Drop the file at /font-tools/font-format-identifier to confirm your manual read — the JSON echoes the magic in hex so you can compare byte-for-byte.
Master font magic-byte table
Every value the JAD identifier matches, plus the most common non-font magics you'll meet when a download went wrong. sfnt magic is big-endian.
| Format | Hex magic | ASCII | Origin | Reported as |
|---|---|---|---|---|
| TrueType (TTF) | 00 01 00 00 | (none) | Apple, 1991 → OpenType sfnt | ttf |
| TrueType (Apple alias) | 74 72 75 65 | true | Apple legacy variant | ttf |
| OpenType/CFF (OTF) | 4F 54 54 4F | OTTO | Adobe + Microsoft, 1996 | otf |
| WOFF 1.0 | 77 4F 46 46 | wOFF | W3C, 2009 (zlib) | woff |
| WOFF 2.0 | 77 4F 46 32 | wOF2 | W3C, 2018 (Brotli) | woff2 |
| TrueType Collection | 74 74 63 66 | ttcf | Apple/MS multi-face | ttc |
| ZIP (not a font) | 50 4B 03 04 | PK.. | PKZIP | unknown |
| PNG (not a font) | 89 50 4E 47 | .PNG | screenshot mislabelled | unknown |
What follows the magic
Byte layout immediately after the 4-byte magic, and the fields the JAD tool surfaces from each. Offsets are from file start.
| Format | Offset 4 | Offset 16 | Tool surfaces |
|---|---|---|---|
| TTF / OTF | numTables (uint16) — table count | (table directory entries) | Tables |
| WOFF | flavor (uint32) — wrapped sfnt version | totalSfntSize (uint32) | Wrapped flavor, Wrapped sfnt size (KB) |
| WOFF2 | flavor (uint32) — wrapped sfnt version | totalSfntSize (uint32) | Wrapped flavor, Decompressed size (KB) |
| TTC | numFonts (uint32) — face count | (offset table) | nothing beyond the magic |
| unknown | n/a | n/a | the raw magic hex in the message |
Cookbook
Annotated hex headers for each format, plus a tiny detector you can paste into your own tools. Confirm any of these against /font-tools/font-format-identifier — it echoes the magic in hex.
TTF header — first 6 bytes
Examplesfnt version 0x00010000 followed by the table count (here 0x0012 = 18 tables).
Offset 00 01 02 03 04 05
Bytes 00 01 00 00 00 12
└ magic ───┘ └tbls┘
format: ttf, Tables: 18OTF header — the OTTO tag
ExampleASCII 'OTTO' marks CFF outlines; table count follows.
Offset 00 01 02 03 04 05
Bytes 4F 54 54 4F 00 0C
ASCII O T T O
└ magic ───┘ └tbls┘
format: otf, Tables: 12WOFF2 header — magic + flavor + size
ExamplewOF2 magic, the wrapped sfnt flavor at offset 4, and the decompressed size at offset 16.
Offset 00 01 02 03 | 04 05 06 07 | ... | 16 17 18 19 Bytes 77 4F 46 32 | 00 01 00 00 | ... | 00 04 C4 00 ASCII w O F 2 format: woff2 Wrapped flavor: 0x00010000 (TrueType inside) Decompressed size: 312.0 KB
An 'unknown' result — a ZIP in disguise
ExampleA foundry archive renamed to .woff2. The magic reveals it's a ZIP.
Offset 00 01 02 03 Bytes 50 4B 03 04 ASCII P K format: unknown, magic: 0x504B0304 → It's a ZIP. Unzip it to find the real font.
A 6-line detector for your own tooling
ExampleThe whole identification is one big-endian read and a switch — the same shape the JAD tool uses internally.
const m = new DataView(buf).getUint32(0, false); // big-endian const fmt = m === 0x00010000 || m === 0x74727565 ? "ttf" : m === 0x4f54544f ? "otf" : m === 0x774f4646 ? "woff" : m === 0x774f4632 ? "woff2" : m === 0x74746366 ? "ttc" : "unknown";
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.
EOT (Embedded OpenType)
Not decodedEOT files begin with a fixed-size header whose identifying bytes are NOT at offset zero — the format version and magic sit deeper in the structure. The JAD tool doesn't decode EOT (and the upload gate won't accept .eot). EOT is IE6–8 only and effectively dead; convert your real font to WOFF2 instead.
SVG fonts
unknownSVG fonts are XML, not binary — their "magic" is an XML/SVG namespace declaration (often starting with < = 0x3C), not an sfnt magic. The tool reports unknown. SVG fonts were dropped by browsers around 2018; no current engine renders them inline.
Apple datafork / dfont
unknownOld Mac suitcase and dfont files store the sfnt in a resource fork, so the bytes at offset zero are not a font magic. The tool can't see the embedded font. Extract the sfnt on the desktop (fontforge) before identifying it.
CFF2 / variable OpenType
Reports otfA variable OpenType font with a CFF2 table still carries the OTTO magic at offset zero, so it reports otf. The magic distinguishes the container flavor, not the CFF version — read tables to learn it's variable.
Bitmap fonts (BDF, PCF, FON)
Not supportedLegacy bitmap formats use ASCII or platform-specific headers, not sfnt magic. They fail the upload gate and would read as unknown if forced. They're console/display relics with no web relevance.
A truncated header (under 16 bytes)
Magic onlyThe format is read from the first 4 bytes, but the wrapper extras (Wrapped flavor @4, sizes @16) require at least 16 bytes. A truncated WOFF still reports woff but omits the extra fields rather than reading past the buffer end.
Big-endian assumption
By designsfnt magic is big-endian, so the detector reads getUint32(0, false). If you write your own detector with a little-endian read, every comparison fails. This is the single most common reimplementation bug.
PostScript Type 1 (.pfb/.pfa)
unknownClassic Type 1 fonts (PFB starts with 0x80 0x01, PFA is ASCII) predate sfnt and have no sfnt magic. The tool reports unknown. Convert to OpenType on the desktop before web use.
Frequently asked questions
What is a font 'magic byte'?
A fixed signature at file offset zero that identifies the format independent of the filename. For TTF/OTF it's the sfnt version (0x00010000 or OTTO); for WOFF/WOFF2 it's a four-byte tag (wOFF/wOF2); for collections it's ttcf.
Is the magic big-endian or little-endian?
Big-endian. Read it with getUint32(0, false) in a DataView, or readUInt32BE in Node. A little-endian read flips the bytes and breaks every comparison.
Why is `00 01 00 00` TrueType?
It's the sfnt version number 1.0 in fixed-point, chosen as the original sfnt magic. OpenType/CFF later used the ASCII tag OTTO to mark CFF outlines while keeping the same container.
What's the `true` magic?
Apple's legacy TrueType magic (74 72 75 65). It's functionally TrueType, so the JAD identifier folds it into the ttf format rather than reporting unknown.
What's the magic for EOT?
EOT doesn't use a simple offset-zero magic — it has a fixed header with version/flag fields deeper in the structure. The JAD tool doesn't decode EOT and won't accept .eot uploads; it's IE6–8 only and effectively dead.
What about SVG fonts?
SVG fonts are XML, so their 'magic' is an XML/SVG declaration, not an sfnt signature. The tool reports unknown. No current browser renders SVG fonts inline (dropped ~2018).
How do I tell WOFF from WOFF2 from the bytes?
WOFF is wOFF (77 4F 46 46); WOFF2 is wOF2 (77 4F 46 32). They differ only in the last byte. WOFF uses zlib, WOFF2 uses Brotli — feeding one to the other's decoder yields garbage.
What follows the magic in an sfnt file?
A uint16 at offset 4 holds the table count (numTables), then the rest of the sfnt directory header, then the table records. The JAD tool surfaces that table count as Tables.
What do WOFF/WOFF2 store at offset 16?
The total uncompressed sfnt size as a uint32. The JAD tool reads it directly (without decompressing) and shows it as Wrapped sfnt size (WOFF) or Decompressed size (WOFF2).
Can I write my own identifier?
Yes — it's one big-endian uint32 read and a switch over the known values (see the 6-line example). The JAD tool wraps that with size info, table-count extraction, and human-readable descriptions, but the core check is trivial.
What if the magic isn't in the table?
It returns unknown, and the echoed hex usually tells you the real type: 0x504B0304 is a ZIP, 0x89504E47 is a PNG, a leading 0x3C (<) is often an HTML error page saved with a font name.
Are there fonts without offset-zero magic?
Yes — EOT (header deeper in), Apple dfont/suitcase (sfnt in the resource fork), and legacy bitmap/Type 1 formats. The JAD tool can't read these from offset zero; convert them to OpenType/WOFF2 on the desktop first.
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.