How to encode any font file as a base64 data uri
- Step 1Pick the lightest format you can — ideally WOFF2 — Base64 inflates whatever you feed it by 1.333x, so encode the smallest binary. WOFF2 is Brotli-compressed and usually 20-50% smaller than the same font as WOFF or TTF. If you only have a TTF, convert it first with [ttf-to-woff2](/font-tools/ttf-to-woff2), then encode the WOFF2 here. If the font covers far more glyphs than you'll use, subset it with [font-subsetter](/font-tools/font-subsetter) before encoding.
- Step 2Drop the font onto the encoder — The picker accepts `.ttf`, `.otf`, `.woff`, and `.woff2`. The extension is checked first, then the real format is read from the file's 4-byte magic — `wOF2` for WOFF2, `wOFF` for WOFF, `OTTO` for OpenType/CFF, `0x00010000` (or Apple's `true`) for TrueType. That magic decides the MIME and the CSS `format()` keyword.
- Step 3Click Process — `FileReader` reads the bytes into an `ArrayBuffer`, the encoder walks it in 32 KB chunks through `btoa` (chunking avoids a call-stack overflow on large fonts), and the result is a `data:` URI plus a wrapping `@font-face` block. There is no options panel for this tool — it does one thing with no toggles.
- Step 4Read the metrics chips — The output shows Source format (e.g. `WOFF2`), Base64 size in KB, and Overhead percent (base64 bytes vs the original file). Overhead is reported against the raw binary, so an already-tiny WOFF2 still shows roughly +33%. Original and Output byte counts appear alongside.
- Step 5Copy the @font-face block or download the .base64.css file — Copy to clipboard puts the whole block on your clipboard; Download saves it as `<stem>.base64.css`. The preview pane shows the first 4,000 characters of a large block and notes it is truncated — the downloaded/copied file is always the complete payload.
- Step 6Paste it and add a system fallback — Drop the block into your stylesheet and reference the family with a fallback stack, e.g. `font-family: "YourFont", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;`. The tool emits only `font-family`, `src`, and `font-display: swap` — add `font-weight` and `font-style` yourself if this is one weight of a family, so the browser maps weights correctly. For a multi-weight builder with those controls, use [font-face-generator](/font-tools/font-face-generator).
What the encoder reads and what it writes
The tool sniffs the 4-byte magic, not the file extension. The MIME goes inside the data URI; the format() keyword is a separate CSS hint outside the URI.
| Detected format (magic) | MIME in data URI | CSS format() keyword | Notes |
|---|---|---|---|
WOFF2 (wOF2) | font/woff2 | format("woff2") | Preferred — already Brotli-compressed, so the smallest base64 payload |
WOFF (wOFF) | font/woff | format("woff") | zlib-compressed; larger than WOFF2 for the same glyphs |
OpenType/CFF (OTTO) | font/otf | format("opentype") | Note the keyword is opentype, not otf — that's the correct CSS spelling |
TrueType (0x00010000 or true) | font/ttf | format("truetype") | Uncompressed sfnt; base64 of a TTF compresses far worse than a WOFF2 under gzip |
Per-tier file-size limit for the encoder
Base64 inflation is fixed at 1.333x regardless of tier; the size cap is on the input font file. There is no glyph cap on this tool — encoding copies bytes, it doesn't parse the glyph table.
| Tier | Max input font | Batch files | Practical note |
|---|---|---|---|
| Free | 5 MB | 1 | Comfortably covers any reasonable web font; a full multilingual TTF can approach it |
| Pro | 50 MB | 20 | Handles large CJK or icon fonts you'd never actually inline in production |
| Pro + Media | 200 MB | 100 | Effectively unbounded for real fonts |
| Developer | 1 GB | Unlimited | For pipeline use; pair the runner to encode locally |
Exactly what the @font-face block contains
The tool emits these properties and only these. Anything else — font-weight, font-style, unicode-range — you add yourself.
| Property | Emitted? | Value source |
|---|---|---|
font-family | Yes | Your filename with the extension stripped (e.g. Inter-Regular.woff2 becomes Inter-Regular) |
src: url("data:...") format(...) | Yes | Quoted data URI with the detected MIME, plus the matching format() keyword |
font-display | Yes — always swap | Hard-coded to swap; edit it if you need optional or block |
font-weight / font-style | No | Add manually — without them the browser assumes 400 / normal for this family |
unicode-range | No | Add manually if you split a family across subset files |
Cookbook
Copy-paste patterns built around what this tool actually outputs. The base64 payload is abbreviated as <BASE64> — the tool emits the full string in the quoted url("...") form.
The exact block the tool produces
ExampleDrop Inter-Regular.woff2 and Process — this is the literal shape of the output, family name taken from the filename stem, data URI double-quoted, font-display: swap appended.
@font-face {
font-family: "Inter-Regular";
src: url("data:font/woff2;base64,<BASE64>") format("woff2");
font-display: swap;
}Add the weight and a fallback before you ship it
ExampleThe tool doesn't emit font-weight/font-style. If this is the 400 weight of a family, declare it so the browser maps bold/italic correctly, and reference it with a system fallback so text still renders if the rule is dropped.
@font-face {
font-family: "Brand"; /* renamed from the file stem */
font-weight: 400; /* added by hand */
font-style: normal; /* added by hand */
font-display: swap;
src: url("data:font/woff2;base64,<BASE64>") format("woff2");
}
body {
font-family: "Brand", -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
}Encoding an OTF — note the format keyword
ExampleDrop an OTTO-magic OpenType/CFF file and the tool writes MIME font/otf but the CSS format keyword opentype. They intentionally differ — opentype is the correct CSS spelling.
@font-face {
font-family: "DisplaySerif";
src: url("data:font/otf;base64,<BASE64>") format("opentype");
font-display: swap;
}Inline the critical weight, link the rest
ExampleYou rarely want to inline a whole family. Inline the one above-the-fold weight from this tool, then point the others at normal URLs so they stay cacheable and load in parallel.
/* Critical: inlined from font-to-base64 */
@font-face {
font-family: "Brand";
font-weight: 400;
font-display: swap;
src: url("data:font/woff2;base64,<BASE64>") format("woff2");
}
/* Secondary weights: external, cacheable */
@font-face {
font-family: "Brand";
font-weight: 700;
font-display: swap;
src: url("/fonts/Brand-Bold.woff2") format("woff2");
}If the font fails after a CSP is added
ExampleThe single most common silent failure: a font-src directive without data:. The CSS parses, the rule survives, but the browser refuses the font fetch. Add data: to font-src.
# Before — inlined font silently blocked: Content-Security-Policy: font-src 'self'; # After — data: URIs allowed: Content-Security-Policy: font-src 'self' data:;
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.
The family name is your filename, not the font's internal name
By designfont-family comes from the filename stem (extension stripped), so Inter-Regular.woff2 becomes "Inter-Regular" — not the name table's family. If you upload a file called font (1).woff2, that's your CSS family name, parentheses and all. Rename the file before encoding, or just edit the family name in the output before pasting.
font-weight and font-style are not emitted
By designThe block has only font-family, src, and font-display: swap. If you inline several weights of one family and give them the same font-family without distinct font-weight values, the browser can't tell them apart and bold/regular collide. Add font-weight and font-style to each block manually, or build the family with font-face-generator, which exposes those controls.
font-display is hard-coded to swap
By designEvery block ships font-display: swap, which shows fallback text immediately then swaps in the web font. For brand-critical headlines you may prefer block or optional; the tool won't change it for you. Edit the value, or read font-display-strategy to pick the right one per use case.
Extension says .ttf but the bytes are OpenType
PreservedThe picker validates the extension, but the MIME and format() come from the 4-byte magic. A file named Brand.ttf whose bytes start with OTTO is encoded as font/otf / format("opentype") — correct, even though the filename lied. The font-family will still be Brand (from the name), but the format hint will be right.
A file with an unrecognised magic still encodes
Encoded as-isThe encoder doesn't validate that the bytes are a real font — it copies whatever's in the buffer. If the 4-byte magic isn't one of the known font signatures, detection falls through to the TrueType default, so you'd get font/ttf / format("truetype") wrapped around non-font bytes. The browser then silently fails to parse it as a font. Confirm the format first with font-format-identifier if in doubt.
CSP font-src without data: blocks the inlined font
Font request rejectedIf your page sends a Content-Security-Policy with a font-src directive (or only default-src), it must include data: or the browser refuses the data URI with Refused to load the font 'data:...'. The CSS still parses fine; only the font fetch is blocked. Fix the header to font-src 'self' data:.
Outlook on Windows desktop ignores the @font-face entirely
Stripped by Word engineOutlook on Windows renders email through Microsoft Word's HTML engine, which strips every @font-face rule — inline or external. There is no workaround that puts your web font into Outlook desktop. Design the fallback so the email reads like itself; the full pattern is in the HTML email embedding guide.
A line-wrapped base64 payload can be dropped by minifiers
Rule fails if wrappedThis tool emits the base64 on a single unwrapped line, which is correct. But if you reformat the output with a pretty-printer that wraps the payload across lines, lightningcss (Next.js, Tailwind v4) can silently drop the whole @font-face rule, leaving only the font-family. Keep the base64 contiguous — never line-wrap it.
TTF base64 compresses far worse than WOFF2 base64
Expected — use WOFF2WOFF2 is already Brotli-compressed, so gzipping its base64 CSS lands within ~1% of the raw binary. A TTF still has internal table redundancy; base64 scrambles it, and gzipping the base64 TTF can be ~17% larger than gzipping the raw TTF. If you must inline, inline WOFF2 — convert with ttf-to-woff2 first.
The preview pane truncates at 4,000 characters
Preview onlyLarge fonts produce blocks longer than the preview shows; the pane caps at 4,000 chars and labels the rest truncated. The Copy and Download actions always emit the full block — the truncation is purely cosmetic so the page stays responsive.
Frequently asked questions
What exactly does this tool output — a raw string or CSS?
A complete CSS @font-face block, not just the raw base64. It contains font-family (from your filename stem), src: url("data:font/...;base64,...") format(...) with the data URI double-quoted, and font-display: swap. You can paste it straight into a stylesheet. If you only want the bare data URI, copy the substring inside url("...").
Does it detect the format, or do I have to tell it?
It detects automatically by reading the file's first 4 bytes (the magic number): wOF2 for WOFF2, wOFF for WOFF, OTTO for OpenType/CFF, and 0x00010000 (or Apple's true) for TrueType. The MIME and the CSS format() keyword both follow from that — even if the file extension is wrong.
What's the actual size overhead?
Base64 is mathematically 4 characters per 3 bytes — a fixed 1.333x, about +33%. The metrics chip reports the overhead of the base64 against your original file. The CSS wrapper (@font-face { ... }) adds a flat handful of bytes on top, negligible past a few KB.
Are there any options or toggles?
No. This tool has no options panel — drop a file, click Process, get the block. font-display is always swap, and font-weight/font-style are never emitted. If you need those controls (multiple weights, custom display value, unicode-range), use font-face-generator instead.
Will my font be uploaded anywhere?
No. FileReader reads the bytes and btoa encodes them entirely in your browser. The result panel even shows a 0 bytes uploaded badge. For signed-in users a single anonymous run counter is recorded for dashboard stats — never the file content.
What's the biggest file I can encode?
5 MB on Free, 50 MB on Pro, 200 MB on Pro + Media, and 1 GB on Developer. The cap is on the input font; base64 inflation is the same 1.333x at every tier. There's no glyph-count limit for this tool because encoding copies bytes rather than parsing the glyph table.
Why is the data URI wrapped in double quotes?
The tool emits url("data:...") quoted. Quoting is valid CSS and avoids edge cases where unquoted url() content interacts badly with certain parsers. The base64 itself stays on a single line — never insert whitespace into it, or lightningcss may drop the whole rule.
Should I include WOFF or TTF fallbacks in the same rule?
Almost never for an inlined font. Every browser since 2018 supports WOFF2, so inline only the WOFF2 — each extra inlined format adds another +33% to render-blocking CSS. If you must support an ancient browser, link the WOFF externally and inline only the WOFF2.
Does encoding change the font's rendering?
No. Decoding the base64 yields byte-identical bytes to the source font. Hinting, kerning, OpenType features, and color tables are all preserved — base64 only changes the transport encoding, not the font.
My inlined font isn't showing up and there's no error — why?
Usual suspects, in order: a font-src CSP directive missing data:; the base64 got line-wrapped and a minifier dropped the rule; you reused the same font-family for several weights without font-weight values; or the browser is Outlook-on-Windows, which strips @font-face outright. Open DevTools Network and search for data:font — if it's absent, the rule was dropped before fetch.
Should I inline fonts on a normal website?
Usually not. On HTTP/2 an external WOFF2 with <link rel="preload"> loads in parallel and stays cacheable across pages, which is faster than render-blocking inlined CSS. Inline only for HTML email, kiosk/offline builds, or single-file demos. The full comparison is in base64 fonts vs external files.
Can I run this from a script instead of the browser?
Yes. GET /api/v1/tools/font-to-base64 returns the (option-less) schema; pair the @jadapps/runner once and POST the font to 127.0.0.1:9789/v1/tools/font-to-base64/run to encode locally. The build-time pattern — encode WOFF2 fonts and inject them into CSS at deploy — is covered in the automation guide.
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.