
How to set up custom fonts in WordPress?
- Sajjad
- Typography
- 11 Oct, 2026
You set up custom fonts in WordPress in one of three main ways, depending on your theme. In a block theme, you can upload or install fonts through the built-in Font Library in the Site Editor, or register them in the theme's theme.json file with the fontFace property. In a classic theme, you add an @font-face stylesheet to the theme and enqueue it from functions.php, then apply the font with ordinary CSS. Whichever route you take, the most reliable setup is to self-host WOFF2 files, load them once, and make sure the block editor uses the same fonts as the front end.
Fonts are one of the most visible parts of a WordPress site, and they're also a common source of slow pages, layout shift and privacy complaints when they're loaded carelessly from third-party servers. In this article you'll learn how WordPress handles fonts today, how to use the Font Library, how to register fonts in theme.json, how to add fonts to a classic or child theme, how to load fonts in the editor, and how to keep everything fast.
How WordPress Handles Fonts
WordPress itself doesn't impose a font. What you see on the front end comes from your theme, and sometimes from plugins or page builders that add their own font controls. Since WordPress 6.5, core also includes the Font Library, which lets site administrators manage fonts for block themes without editing code.
There are a few moving parts to keep in mind:
- Font files: The actual WOFF2 (or WOFF, TTF, OTF) files that browsers download.
- Font faces: The
@font-facedeclarations that tell browsers which file to use for which family, weight and style. - Font families: The named presets your theme exposes, such as "Inter" or "Lora", which become options in the editor's typography controls.
- Styles: The CSS that applies a family to body text, headings, buttons and so on.
In a block theme, theme.json handles all four. In a classic theme, you handle them yourself with files and CSS.
Block Theme or Classic Theme?
Check which kind of theme you have before you start. If the Appearance menu shows an Editor item and no Customize item by default, you're using a block theme. Themes such as Twenty Twenty-Four and Twenty Twenty-Five are block themes. If you see Customize, Widgets and Menus, it's a classic theme. Some hybrid themes use theme.json for settings but still render with PHP templates; for fonts they behave much like block themes.
Option 1: Use the Font Library in the Site Editor
The Font Library is the quickest route for block themes and needs no code.
- Go to Appearance > Editor > Styles.
- Open Typography, then select Manage fonts (the icon next to the font list).
- Choose the Upload tab to add your own font files, or the Install Fonts tab to browse Google Fonts.
- If you use Install Fonts, WordPress asks for permission to connect to Google Fonts. The selected fonts are then downloaded to your own server rather than loaded from Google at runtime.
- Select the weights and styles you need, then install or upload them.
- Back in Typography, pick the new font for Text, Headings, Links, Captions or Buttons, and save.
Uploaded and installed fonts are stored in wp-content/uploads/fonts by default, so they survive theme updates and theme switches. WordPress generates the @font-face rules for you.
When the Font Library Is Enough
The Font Library suits most small and medium sites. Its limitations are worth knowing:
- It only applies to block themes: Classic themes don't get the Site Editor typography panel.
- It doesn't optimise files for you: If you upload a 400 KB TTF, that's what visitors download. Convert to WOFF2 and subset first.
- Selections live in the database: Choices made in the Site Editor are saved as user global styles. If you develop a theme for distribution or version control, register fonts in
theme.jsoninstead.
Option 2: Register Fonts in theme.json
For a theme you build or maintain, registering fonts in theme.json keeps everything in code. Place your font files inside the theme, for example:
my-theme/
├── assets/
│ └── fonts/
│ ├── inter/
│ │ ├── Inter-Variable.woff2
│ │ └── Inter-Variable-Italic.woff2
│ └── lora/
│ └── Lora-Variable.woff2
├── style.css
└── theme.json
Then declare the families and their faces. The file:./ prefix tells WordPress the path is relative to the theme folder:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"typography": {
"fontFamilies": [
{
"name": "Inter",
"slug": "inter",
"fontFamily": "Inter, system-ui, -apple-system, \"Segoe UI\", sans-serif",
"fontFace": [
{
"fontFamily": "Inter",
"fontStyle": "normal",
"fontWeight": "100 900",
"fontDisplay": "swap",
"src": ["file:./assets/fonts/inter/Inter-Variable.woff2"]
},
{
"fontFamily": "Inter",
"fontStyle": "italic",
"fontWeight": "100 900",
"fontDisplay": "swap",
"src": ["file:./assets/fonts/inter/Inter-Variable-Italic.woff2"]
}
]
},
{
"name": "Lora",
"slug": "lora",
"fontFamily": "Lora, Georgia, serif",
"fontFace": [
{
"fontFamily": "Lora",
"fontStyle": "normal",
"fontWeight": "400 700",
"fontDisplay": "swap",
"src": ["file:./assets/fonts/lora/Lora-Variable.woff2"]
}
]
}
]
}
},
"styles": {
"typography": {
"fontFamily": "var:preset|font-family|inter"
},
"elements": {
"heading": {
"typography": {
"fontFamily": "var:preset|font-family|lora",
"fontWeight": "600"
}
}
}
}
}
A few details matter here:
- The fontFamily string is the full stack: Include sensible fallbacks after the custom font so text still renders well if the file fails to load.
- The fontWeight range: For a variable font, give the full range, such as
100 900. For static fonts, add onefontFaceentry per file with a single weight like400or700. - The slug becomes a CSS variable: WordPress outputs
--wp--preset--font-family--inter, which you can reuse in custom CSS, and adds ahas-inter-font-familyclass for blocks that use the preset.
You can see the generated output by viewing the page source. WordPress prints the font faces in a style element with the ID wp-fonts-local:
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('https://example.com/wp-content/themes/my-theme/assets/fonts/inter/Inter-Variable.woff2') format('woff2');
}
Using the Preset in Custom CSS
Once a family is registered, refer to it by its variable rather than repeating the name. This keeps everything in sync if you later change the stack in theme.json:
.site-tagline {
font-family: var(--wp--preset--font-family--lora);
font-style: italic;
}
Option 3: Add Fonts to a Classic Theme
Most classic themes don't have a theme.json file with font presets, so the dependable approach is a small stylesheet with your own @font-face rules. This works in every theme and every WordPress version.
Create assets/css/fonts.css in your theme (or child theme):
@font-face {
font-family: "Source Sans 3";
src: url("../fonts/source-sans-3/SourceSans3-Variable.woff2") format("woff2");
font-weight: 200 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Source Sans 3";
src: url("../fonts/source-sans-3/SourceSans3-Variable-Italic.woff2") format("woff2");
font-weight: 200 900;
font-style: italic;
font-display: swap;
}
:root {
--font-body: "Source Sans 3", system-ui, -apple-system, "Segoe UI", sans-serif;
}
body {
font-family: var(--font-body);
}
The url() paths are relative to the CSS file, not to the theme root. Then enqueue the stylesheet in functions.php:
<?php
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style(
'mytheme-fonts',
get_stylesheet_directory_uri() . '/assets/css/fonts.css',
array(),
wp_get_theme()->get( 'Version' )
);
} );
Use get_stylesheet_directory_uri() in a child theme so the path points to the child, not the parent. In a parent theme, get_template_directory_uri() or get_theme_file_uri() works too.
Overriding a Parent Theme's Fonts
Many commercial themes load Google Fonts from their own handle. If you're replacing those fonts in a child theme, find the handle the parent uses (search its functions.php for wp_enqueue_style and fonts.googleapis.com) and dequeue it after it has been registered:
<?php
add_action( 'wp_enqueue_scripts', function () {
// Replace 'parent-google-fonts' with the handle used by your parent theme.
wp_dequeue_style( 'parent-google-fonts' );
wp_deregister_style( 'parent-google-fonts' );
}, 20 );
The priority of 20 makes sure this runs after the parent's own enqueue at the default priority of 10. Check the theme's options panel first, though; many themes have a setting to disable their Google Fonts.
Load the Same Fonts in the Block Editor
If the editor shows a different font from the front end, writers get a misleading preview. Block themes using theme.json or the Font Library handle this automatically. Classic themes need to opt in to editor styles:
<?php
add_action( 'after_setup_theme', function () {
add_theme_support( 'editor-styles' );
add_editor_style( 'assets/css/fonts.css' );
} );
add_editor_style() takes a path relative to the theme directory and loads it inside the editor canvas. WordPress scopes the rules so they don't leak into the editor's interface, and url() paths inside the file are rewritten so the fonts still resolve.
Keep Custom Fonts Fast
Adding a font is easy; adding one without slowing the site down takes a little more care.
Use WOFF2 and Subset Your Files
WOFF2 is supported by every current browser and is usually much smaller than TTF or OTF. If your licence allows it, remove characters you don't need. With the fonttools package installed (pip install fonttools brotli), you can subset to Latin characters like this:
pyftsubset Inter-Variable.ttf \
--unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD" \
--layout-features="*" \
--flavor=woff2 \
--output-file=Inter-Variable.woff2
Compare sizes before and after:
ls -lh Inter-Variable.ttf Inter-Variable.woff2
-rw-r--r-- 1 you staff 852K Inter-Variable.ttf
-rw-r--r-- 1 you staff 94K Inter-Variable.woff2
Your exact numbers will depend on the font version and the characters you keep. For more on this, see what font subsetting is and how it reduces file size.
Load Fewer Weights
Each static weight and style is a separate download. Regular, italic, semibold and bold are usually plenty for body text. A variable font replaces several static files with one, which often makes it the better choice once you need three or more weights.
Preload the Main Text Font
Preloading tells the browser to fetch a font early, before it discovers it in the CSS. Preload only the one or two files used above the fold, typically the regular body weight:
<?php
add_action( 'wp_head', function () {
printf(
'<link rel="preload" href="%s" as="font" type="font/woff2" crossorigin>' . "\n",
esc_url( get_theme_file_uri( 'assets/fonts/inter/Inter-Variable.woff2' ) )
);
}, 1 );
The crossorigin attribute is required for font preloads even on the same origin, or the browser will download the file twice. The URL must match the one in your @font-face rule exactly, including any query string.
Avoid Duplicate Font Loading
Plugins, page builders and themes often each load their own fonts. Open your browser's developer tools, filter the Network panel by Font, and reload the page. If you see the same family from two sources, or requests to fonts.gstatic.com when you meant to self-host, track down the source and disable it.
Google Fonts and Privacy
Loading fonts directly from Google's servers sends each visitor's IP address to Google. A German court ruled in 2022 that doing this without consent breached the GDPR, and many European site owners now self-host as a result. Self-hosting also removes a third-party connection and puts caching under your control.
Google Fonts are released under open licences, mostly the SIL Open Font License, so you're free to download and host them yourself. The Font Library's Install Fonts tab already does this. For classic themes, download the files, convert or subset them if needed, and use the stylesheet approach above.
Troubleshooting Custom Fonts
When a font doesn't appear, work through these checks in order:
- Check the Network panel: Is the font file requested at all? A 404 means a wrong path; remember that
url()paths in CSS are relative to the stylesheet. - Check the computed style: In the Elements panel, look at the Computed tab and the Rendered Fonts section. It shows which font the browser actually used.
- Match the family name exactly: The name in
font-familymust match the name in@font-face, including spaces. - Clear caches: Caching plugins, CDNs and server caches can hold an old stylesheet. Bump the version number in
wp_enqueue_styleto force a fresh file. - Check CORS on a CDN: If fonts are served from a different domain, that server must send an
Access-Control-Allow-Originheader or the browser will refuse to use them. - Check for overrides: Global styles saved in the Site Editor take priority over
theme.jsondefaults. Reset typography in Styles if your code changes seem ignored.
FAQ: Custom Fonts in WordPress
Not usually. Block themes can use the built-in Font Library or theme.json, and classic themes can load fonts with a small stylesheet enqueued from functions.php. Plugins are mainly useful if you want a settings screen for a classic theme.
Use WOFF2. It is supported by all current browsers and is normally the smallest format. Convert TTF or OTF files to WOFF2 before uploading, as long as your font licence allows web use.
By default, in the wp-content/uploads/fonts folder. Because they are outside the theme folder, they stay available when you update or change your theme.
Fonts added through the Font Library or placed in a child theme are safe. Fonts you add directly to a third-party parent theme's folder will be overwritten when that theme updates, so use a child theme instead.
The editor canvas needs its own copy of the font styles. Block themes handle this automatically. In classic themes, add editor-styles theme support and call add_editor_style with your fonts stylesheet.
Yes. Google Fonts are published under open licences, mostly the SIL Open Font License, which allow downloading, self-hosting and commercial use. Check the licence file included with each family to be sure.
Conclusion
Setting up custom fonts in WordPress comes down to matching the method to your theme. Block themes give you the Font Library for point-and-click management and theme.json for version-controlled font presets, while classic themes rely on a small @font-face stylesheet enqueued from functions.php and mirrored in the editor with add_editor_style().
Whichever route you choose, self-host WOFF2 files, load only the weights you use, preload the main text font and check the Network panel for duplicate requests. Those few habits give you the typography you want without the speed, layout and privacy problems that often come with fonts added in a hurry.


