Skip to main content
Blog|
How-to guides

How to add custom fonts to WordPress

|
Apr 18, 2026|14 min read
HOW-TO GUIDESHow to add custom fonts toWordPressHOSTNEYhostney.comApril 18, 2026

WordPress themes ship with a small set of fonts their authors chose, and for a lot of sites that is fine. But at some point most people want something specific – a brand font, a font that matches the logo, a typeface the designer picked, or just something that does not look like every other WordPress site. The path from “I want this font” to “the font is live on my site” depends on what kind of theme you are running and where the font comes from, and picking the wrong path wastes an afternoon or leaves you with a site that loads the font from three different places at once.

This guide walks through three methods in order of simplicity – theme settings, block theme theme.json , and fully self-hosted fonts – explains when each is the right choice, and covers the GDPR and performance reasons people increasingly self-host fonts that used to come from Google.

The three methods at a glance#

  • Theme settings (classic themes). Most classic themes have a font picker in the Customizer, often backed by Google Fonts. You pick a font from a dropdown and the theme handles loading. Zero code.
  • theme.json (block themes). Block themes declare their fonts in a JSON file. You either use the Appearance > Editor > Styles > Typography interface, or edit theme.json directly to add or change fonts. Still mostly no-code.
  • Self-hosted with @font-face . You download the font files ( .woff2 ), drop them in your theme, and register them with CSS. Works with any theme, any font source, and is the right choice when you need full control, GDPR compliance, or to use a font that Google Fonts does not carry.

Pick the first method that covers your case. Do not jump to self-hosting because it sounds more professional – if the theme’s built-in picker works, use the built-in picker.

Method 1: Google Fonts via theme settings (classic themes)#

Most classic themes – Astra, GeneratePress, OceanWP, Kadence in classic mode, Divi, Avada, and anything based on a Bootstrap-style starter – expose a font selector in the WordPress Customizer.

  1. In the WordPress admin, go to Appearance > Customize
  2. Look for a section called Typography, Fonts, or Global Styles
  3. Click into the section and you will see per-element font controls (body, headings, buttons, menu items)
  4. Pick a font from the dropdown – the theme usually ships with the entire Google Fonts library available, sometimes plus a few system fonts
  5. Click Publish

The theme handles the rest. It generates the appropriate <link> tag or @font-face declaration and outputs it on every page. No code, no file uploads, no functions.php edits.

When this works well. The font you want is in Google Fonts (there are over 1,500 of them, so this covers most cases), the theme’s typography controls expose the element you want to change, and you are OK with Google Fonts’ CDN delivering the file. For the vast majority of WordPress sites, this is the right answer.

When to skip to a different method. Your font is not in Google Fonts. You need GDPR compliance for an EU-facing site (covered below). The theme’s picker does not expose the element you want to change. Your theme is a block theme (use method 2). You need to match a specific brand font that was licensed from a foundry like Adobe Fonts, MyFonts, or Monotype.

Gotcha: font loading in classic themes. Many classic themes still use the Google Fonts CSS API ( https://fonts.googleapis.com/css?family=... ), which means every visitor makes a DNS lookup to Google, an HTTPS handshake, a CSS fetch, and then fetches the .woff2 files from fonts.gstatic.com . This is slower than self-hosting (two extra network connections), and it is what creates the GDPR problem. Some themes (Astra, GeneratePress) have a “local Google Fonts” option in settings that downloads the font into the uploads directory and serves it from your own domain – if you see that option, turn it on.

Method 2: Google Fonts in block themes via theme.json#

If your site uses a block theme – Twenty Twenty-Four, Twenty Twenty-Five, Spectra One, Frost, or any theme that uses the Full Site Editor – fonts are managed in theme.json and through the Styles interface in Appearance > Editor.

The visual approach

  1. Go to Appearance > Editor
  2. Click Styles in the top-right
  3. Click Typography
  4. Click on an element (Body, Headings, Links, Captions, Buttons) and pick a font

Block themes typically include a handful of fonts out of the box. To add new ones without touching code, WordPress 6.5+ includes a Font Library: in the Styles panel, click Typography, then the Manage fonts icon (the + next to the fonts list). You can install Google Fonts directly from the library or upload .woff2 files from your computer.

The Font Library writes installed fonts into your site’s database and serves them from the uploads directory – they are effectively self-hosted, with no ongoing connection to Google once downloaded. This is the cleanest no-code path on a modern block theme.

The theme.json approach

If you are building a child theme or prefer to manage fonts in code, theme.json has a settings.typography.fontFamilies array. Each entry registers a font family and optionally the files that implement it:

{
  "$schema": "https://schemas.wp.org/wp/6.7/theme.json",
  "version": 3,
  "settings": {
    "typography": {
      "fontFamilies": [
        {
          "name": "Inter",
          "slug": "inter",
          "fontFamily": "Inter, system-ui, sans-serif",
          "fontFace": [
            {
              "fontFamily": "Inter",
              "fontWeight": "400",
              "fontStyle": "normal",
              "src": ["file:./assets/fonts/Inter-Regular.woff2"]
            },
            {
              "fontFamily": "Inter",
              "fontWeight": "700",
              "fontStyle": "normal",
              "src": ["file:./assets/fonts/Inter-Bold.woff2"]
            }
          ]
        }
      ]
    }
  }
}

The file: prefix tells WordPress the font is self-hosted. Drop the .woff2 files into wp-content/themes/your-theme/assets/fonts/ and WordPress generates the @font-face CSS automatically. No functions.php edits needed.

If you just want to reference a Google-hosted font instead, omit fontFace and use the full CSS fontFamily value as you would in any stylesheet. The visual editor will list the family as a choice everywhere fonts can be picked.

Font weights. Each weight is a separate fontFace entry. If you only register 400 and 700, the browser will synthesize 500 and 600 by faking the rendering, which almost always looks bad. Either register every weight you use, or register just the weights you use and only apply those weights in your CSS.

fontFamily stack. Always include a fallback. "fontFamily": "Inter, system-ui, sans-serif" means if Inter has not loaded yet, the browser uses the OS default sans-serif. This prevents the “invisible text” problem during the font load – the page shows text in the fallback font, then swaps to Inter when it arrives. Pair this with font-display: swap in your @font-face (WordPress does this automatically when you use file: in theme.json ) and the behaviour is correct out of the box.

Method 3: Self-hosting custom fonts with @font-face#

If neither of the above covers your case – classic theme without a built-in loader, font that is not in Google Fonts, GDPR-driven decision to drop Google entirely, or a licensed brand font – you self-host the font files and register them with CSS yourself. This is the most work but also the approach that gives you full control.

Step 1: Get the font files

You need .woff2 at minimum. .woff2 is supported in every browser in current use (even Safari on iOS 10+ handles it), and it is the smallest of the modern formats thanks to Brotli compression. .ttf and .otf are the original desktop formats; they work, but they are roughly twice the size of the equivalent .woff2 . Older .woff is unnecessary on a site that does not need to support IE11.

For a licensed foundry font, the vendor provides the files directly after purchase. For a free font from Google Fonts that you want to self-host, use the google-webfonts-helper tool (gwfh.mranftl.com) – it generates the exact .woff2 files and CSS you need, configured for the weights and character sets you pick. For a font you already have as .ttf or .otf , convert it to .woff2 using woff2_compress (the official Google tool) or any of the web-based converters.

Do not serve .ttf or .otf from the public internet if you can avoid it. They work, but they are unnecessarily large and the performance cost compounds across pages.

Step 2: Upload the fonts to your theme

Create a fonts directory inside your theme (or child theme) and put the .woff2 files in it:

wp-content/themes/your-theme/
├── assets/
│   └── fonts/
│       ├── brandname-regular.woff2
│       └── brandname-bold.woff2
├── functions.php
└── style.css

Put them in a child theme if you are using a third-party parent theme. A theme update will wipe wp-content/themes/parent-theme/fonts/ but not your child theme’s directory.

Step 3: Register with @font-face

In your theme’s main stylesheet (or a dedicated fonts.css file), add the @font-face declarations:

@font-face {
    font-family: 'BrandName';
    src: url('assets/fonts/brandname-regular.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'BrandName';
    src: url('assets/fonts/brandname-bold.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

Key settings to get right:

  • font-display: swap is the single most important line. Without it, browsers can hide text for up to 3 seconds while the font downloads, giving you a blank page for slow connections. With swap , the fallback font shows immediately and is replaced when the custom font arrives. This is also what Core Web Vitals’ Cumulative Layout Shift grading expects.
  • format('woff2') tells the browser to only download this file if it can parse .woff2 . Every browser in current use can.
  • One @font-face per weight and style. A separate block for regular, bold, italic, bold-italic. Do not try to combine them into one.

Step 4: Enqueue the stylesheet

If the @font-face declarations are in your theme’s main style.css , they are already loaded on every page – you are done. If you put them in a separate file, enqueue it from functions.php :

function your_theme_enqueue_fonts() {
    wp_enqueue_style(
        'your-theme-fonts',
        get_theme_file_uri('/assets/fonts.css'),
        array(),
        '1.0'
    );
}
add_action('wp_enqueue_scripts', 'your_theme_enqueue_fonts');

Put this in your child theme’s functions.php , not the parent theme’s. get_theme_file_uri() resolves to the child theme’s URL when called from the child theme.

Step 5: Use the font

Apply it in CSS like any other font:

body {
    font-family: 'BrandName', system-ui, sans-serif;
}

h1, h2, h3 {
    font-family: 'BrandName', Georgia, serif;
    font-weight: 700;
}

Always include a fallback stack. If the .woff2 fails to load or has not arrived yet, the browser uses the next font in the list. System fonts ( system-ui , -apple-system , BlinkMacSystemFont ) are the fastest fallback because they require no download.

Preload critical fonts

If your above-the-fold content uses a custom font, preload it so the browser starts fetching it before it would discover the @font-face rule in your stylesheet:

function your_theme_preload_fonts() {
    echo '<link rel="preload" href="' . get_theme_file_uri('/assets/fonts/brandname-regular.woff2') . '" as="font" type="font/woff2" crossorigin>';
}
add_action('wp_head', 'your_theme_preload_fonts', 1);

Only preload the specific weights used above the fold (usually regular body text and the main heading weight). Preloading every weight wastes bandwidth on fonts that may never be used on a given page. The broader performance tradeoff is covered in how to speed up WordPress.

Why self-host instead of using Google Fonts directly#

Two reasons come up repeatedly: GDPR compliance and performance.

GDPR and the Google Fonts problem

In January 2022, a court in Munich ruled that embedding Google Fonts on a website transmits the visitor’s IP address to Google in the US without a valid legal basis under GDPR, and fined the site operator €100. The ruling itself was small, but it triggered a wave of automated warning letters to site operators across Germany and Austria – often demanding settlements of €100-200 to avoid formal complaints. Similar reasoning has since surfaced in other EU member states.

The core issue is that loading a font from fonts.googleapis.com happens before any cookie consent prompt can run. The visitor’s browser connects to Google’s servers and transmits their IP as part of the TCP handshake – that is enough, under the ruling, to constitute personal data processing without consent.

Self-hosting removes this entirely. The font is served from your domain, with no connection to Google at any point. No IP transmission, no consent required, no legal grey area. If your site serves EU visitors and the fonts are fetched from Google’s CDN, self-hosting is now effectively a compliance requirement.

WordPress 6.5’s Font Library (method 2 above) handles this automatically when you install fonts through the library interface – they are stored locally and served from your own domain. Classic themes usually do not handle it automatically; check whether your theme has a “local Google Fonts” option, and if it does not, use a plugin like OMGF (Optimize My Google Fonts) to rewrite the theme’s Google Fonts references to local copies.

Performance

Even without the legal angle, self-hosting is measurably faster:

  • One fewer DNS lookup. fonts.googleapis.com requires DNS resolution before the browser can fetch the CSS. Self-hosted fonts reuse the DNS entry your site already has.
  • One fewer TLS handshake. Same reason – you are reusing the HTTPS connection to your own server instead of opening a new one.
  • HTTP/2 or HTTP/3 multiplexing. Modern HTTP versions can download multiple files in parallel over the same connection. Loading fonts from a different domain means they compete with assets from your site rather than sharing the connection.
  • Better cache control. When you serve the fonts yourself, you set the cache headers. A long Cache-Control: max-age=31536000 on .woff2 files means returning visitors do not re-download the fonts. Google Fonts sets a 1-year cache too, but you have no control over it if the policy changes.

The difference on a typical WordPress site is 100-300ms faster First Contentful Paint, which matters both for user experience and for the Core Web Vitals scores that affect search rankings.

Tips for picking and configuring fonts#

  • Do not use more than two typefaces. One for body, one for headings, maybe a third for UI elements. Every additional family is an additional download and an additional rendering style the reader has to adjust to.
  • Do not use more than four weights total across all families. Each weight is a separate file. Three body weights (regular, bold, italic) and one heading weight cover most designs.
  • Use variable fonts where possible. A single variable font file like Inter or Source Sans can replace five or six static weight files. Browser support is universal now. Variable fonts are registered the same way in @font-face with a font-weight: 100 900 range declaration.
  • Limit character subsets. If your site is English-only, you do not need the full Unicode range. Google Fonts lets you pick subsets; self-hosted fonts from foundries usually ship as Latin-only already.
  • Test on mobile. A font that looks great at 16px on a desktop may look thin and hard to read on a phone. The default body-size increase of mobile browsers means fonts render smaller relative to the viewport.
  • Check the license for self-hosted fonts. Free for personal use is not free for commercial use. Most licensed foundry fonts permit self-hosting, but not all – check before uploading.

Common mistakes#

  • Loading Google Fonts from the theme and also self-hosting a copy. Check the network tab – many sites end up loading the same font from both sources because the theme’s built-in Google Fonts loader was not disabled when the self-hosted version was added. The fix is usually an option in the theme’s settings to disable the built-in font loader.
  • Forgetting font-display: swap . Without it, browsers block rendering of any text that uses the font for up to three seconds. This is the single biggest font-related cause of the “page loads then jumps” user experience.
  • Using .ttf instead of .woff2 . Twice the file size, no compatibility benefit.
  • Not preloading critical fonts. If your logo or hero headline uses a custom font, preload it. Otherwise the browser discovers it while parsing CSS, which is too late for it to show on the first paint.
  • Self-hosting a Google Font for GDPR but leaving the CSS @import to Google. The font files are local but the CSS still references Google. Check the page source – the string fonts.googleapis.com should not appear anywhere.
  • Uploading fonts to the Media Library. WordPress does not expect font files there, the MIME type checks may reject .woff2 , and the files end up in a location that is not ideal for cache headers. Use the theme directory instead.
  • Editing the parent theme’s files. An update wipes them. Use a child theme.
  • Mixing HTTP and HTTPS font sources. A site served over HTTPS that loads a font from http://fonts.googleapis.com triggers a mixed content error. Always use HTTPS, or better, self-host and avoid the issue entirely.

How Hostney handles custom fonts#

Hostney does not impose any special restriction on custom fonts – you can upload .woff2 files to your theme via the file manager, SFTP, or the WordPress admin, and serve them from your own domain just like any other asset. The .woff2 MIME type is configured correctly in nginx, cache headers are set to one year on versioned font filenames (which means returning visitors do not re-download them), and the built-in FastCGI caching layer serves the CSS that references your fonts from cache without re-generating it on every request.

For performance, Hostney’s static-asset delivery already does the things a manual self-hosting setup has to be configured to do: gzip and Brotli compression are enabled for text-based assets, HTTP/2 and HTTP/3 serve font files in parallel with CSS and images over the same connection, and long cache headers keep repeat visits fast. The complete WordPress performance guide covers the full stack of optimizations – font loading is one piece of a larger picture that also includes image optimization, caching, and critical CSS.

If you are building on top of a custom WordPress theme or working with a block theme via theme.json, the fonts live alongside the rest of your theme assets and deploy with your theme update workflow. No special configuration needed.

Summary#

Three methods, in increasing order of control and effort: (1) use your theme’s built-in font picker for Google Fonts if it has one, (2) use theme.json and the Font Library on block themes, (3) self-host .woff2 files with @font-face for licensed fonts, non-Google sources, GDPR compliance, or maximum performance. Do not skip straight to the hardest option – use the simplest method that covers your case. When you do self-host, the three things that matter most are font-display: swap (to avoid invisible text during load), a sensible fallback stack in every font-family rule (to survive font load failures), and preloading the one or two fonts used above the fold (to avoid delayed first paint). Everything else is tuning.

Related articles