Forme includes three standard PDF font families by default: Helvetica, Times, and Courier (each with regular, bold, italic, and bold italic variants), plus Noto Sans (regular and bold) as a builtin Unicode fallback for non-Latin scripts. For any other typeface, register a TrueType (.ttf) font file.
Registered fonts are automatically subsetted — only glyphs used in the document are embedded, keeping file sizes small.
Font.register()
Register fonts globally. Works like react-pdf’s Font.register().
import { Font } from '@formepdf/react';
Font.register({
family: 'Inter',
src: './fonts/Inter-Regular.ttf',
});
Font.register({
family: 'Inter',
src: './fonts/Inter-Bold.ttf',
fontWeight: 'bold',
});
Font.register({
family: 'Inter',
src: './fonts/Inter-Italic.ttf',
fontStyle: 'italic',
});
Then use the font by name in any style:
<Text style={{ fontFamily: 'Inter', fontSize: 14 }}>
Regular text in Inter
</Text>
<Text style={{ fontFamily: 'Inter', fontSize: 14, fontWeight: 'bold' }}>
Bold text in Inter
</Text>
Options
| Option | Type | Default | Description |
|---|
family | string | (required) | Font family name to reference in fontFamily style |
src | string | Uint8Array | (required) | Font source: file path, base64 data URI, or raw bytes |
fontWeight | number | "normal" | "bold" | 400 | Font weight. "normal" = 400, "bold" = 700. |
fontStyle | "normal" | "italic" | "oblique" | "normal" | Font style variant |
Font.clear()
Remove all globally registered fonts. Useful in tests.
Multiple weights in one call
You can register multiple weights for the same family using the fonts array syntax:
Font.register({
family: 'Inter',
fonts: [
{ src: './fonts/Inter-Regular.ttf', fontWeight: 400 },
{ src: './fonts/Inter-Bold.ttf', fontWeight: 700 },
{ src: './fonts/Inter-Italic.ttf', fontWeight: 400, fontStyle: 'italic' },
],
});
Google Fonts
You can register fonts directly from a URL. Google Fonts .woff2 URLs work:
import { Font, Document, Page, Text } from '@formepdf/react';
Font.register({
family: 'Inter',
fonts: [
{ src: 'https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiA.woff2', fontWeight: 400 },
{ src: 'https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuI6fAZ9hiA.woff2', fontWeight: 700 },
],
});
export default function Doc(data) {
return (
<Document>
<Page>
<Text style={{ fontFamily: 'Inter', fontWeight: 700, fontSize: 24 }}>Bold heading</Text>
<Text style={{ fontFamily: 'Inter', fontSize: 12 }}>Regular body text</Text>
</Page>
</Document>
);
}
To find the direct .woff2 URL for a Google Font, open https://fonts.googleapis.com/css2?family=Inter:wght@400;700 in your browser and copy the url() value from the CSS.
Font fallback chains
Specify multiple font families separated by commas. Forme tries each family in order per character, so mixed-script text (e.g., English + Arabic) works automatically:
<Text style={{ fontFamily: 'Inter, Noto Sans' }}>
English text and عربي in the same paragraph
</Text>
If a character isn’t covered by Inter, Forme tries Noto Sans. If no font in the chain covers the character, the builtin Noto Sans is used as a final fallback.
Document fonts prop
Register fonts per-document instead of globally:
<Document fonts={[
{ family: 'Roboto', src: './fonts/Roboto-Regular.ttf' },
{ family: 'Roboto', src: './fonts/Roboto-Bold.ttf', fontWeight: 'bold' },
{ family: 'Roboto', src: './fonts/Roboto-Italic.ttf', fontStyle: 'italic' },
]}>
<Text style={{ fontFamily: 'Roboto' }}>Hello in Roboto</Text>
</Document>
Document fonts and global fonts are merged. If both register the same family + weight + style combination, the document font wins.
Font sources
The src option accepts three formats:
| Format | Example | When to use |
|---|
| File path | './fonts/Inter.ttf' | Local development, CLI dev server |
| Data URI | 'data:font/ttf;base64,AAAA...' | Pre-encoded fonts, bundled assets |
Uint8Array | new Uint8Array(buffer) | Fonts loaded from a database or API |
File paths are resolved relative to the template file in the CLI dev server (forme dev), or relative to the working directory in renderDocument().
Standard fonts
These fonts are always available without registration:
| Family | Weights | Styles | Notes |
|---|
Helvetica | 400, 700 | normal, italic | Default font |
Times | 400, 700 | normal, italic | |
Courier | 400, 700 | normal, italic | |
Noto Sans | 400, 700 | normal | Builtin Unicode fallback |
Automatic fallback: When a character is not covered by your chosen font (e.g., Cyrillic or Greek text with Helvetica), Forme automatically falls back to Noto Sans. This means non-Latin text works out of the box without registering any fonts.
If a fontFamily is not found, Forme falls back to Helvetica.
Example: Multiple weights
import { Font, Document, Page, View, Text } from '@formepdf/react';
import { renderDocument } from '@formepdf/core';
Font.register({ family: 'Inter', src: './fonts/Inter-Regular.ttf' });
Font.register({ family: 'Inter', src: './fonts/Inter-Medium.ttf', fontWeight: 500 });
Font.register({ family: 'Inter', src: './fonts/Inter-SemiBold.ttf', fontWeight: 600 });
Font.register({ family: 'Inter', src: './fonts/Inter-Bold.ttf', fontWeight: 700 });
const pdf = await renderDocument(
<Document>
<Page size="A4" margin={54}>
<Text style={{ fontFamily: 'Inter', fontWeight: 400 }}>Regular (400)</Text>
<Text style={{ fontFamily: 'Inter', fontWeight: 500 }}>Medium (500)</Text>
<Text style={{ fontFamily: 'Inter', fontWeight: 600 }}>SemiBold (600)</Text>
<Text style={{ fontFamily: 'Inter', fontWeight: 700 }}>Bold (700)</Text>
</Page>
</Document>
);
Troubleshooting
Text renders in Helvetica instead of my custom font
The fontFamily in your style must exactly match the family you passed to Font.register(). Font names are case-sensitive.
// Registration
Font.register({ family: 'Inter', src: './fonts/Inter-Regular.ttf' });
// ✅ Correct
<Text style={{ fontFamily: 'Inter' }}>Works</Text>
// ❌ Wrong — case mismatch
<Text style={{ fontFamily: 'inter' }}>Falls back to Helvetica</Text>
Bold or italic text falls back to Helvetica
You need to register each weight and style variant separately. If you register only the regular weight and use fontWeight: 700, Forme will look for a bold variant, not find one, and fall back.
// Only regular registered — bold will fall back
Font.register({ family: 'Inter', src: './fonts/Inter-Regular.ttf' });
// Fix: register the bold variant too
Font.register({ family: 'Inter', src: './fonts/Inter-Bold.ttf', fontWeight: 700 });
Non-Latin characters show as boxes or question marks
The font you’re using doesn’t cover those characters. Either:
- Register a font that covers them (e.g., Noto Sans for broad Unicode support)
- Use a font fallback chain:
fontFamily: 'Inter, Noto Sans'
- Do nothing — Forme’s builtin Noto Sans provides automatic fallback for most scripts