Google Fonts Loader Generator

Enqueue web fonts in WordPress the right way: one request, font-display swap, preconnect hints, and the editor covered as well as the front end.

Live output

Enable JavaScript to customise; default output below.

Serve from

Local files avoid a third-party request, which is both faster and what GDPR guidance in several EU countries expects.

Font families

Each family and weight is more to download. Two families and three weights is a lot already.

Used for local files. The @font-face rules live in that file.

Live preview fonts.php
<?php
/**
 * Web fonts.
 */

/**
 * Builds the stylesheet URL.
 *
 * One request for every family: css2 takes repeated family parameters, and
 * each extra request costs a round trip before any text can be painted.
 *
 * @return string
 */
function my_theme_fonts_url() {
	$families = array(
		'Inter:wght@400;600',
	);

	// Spaces in a family name are plus signs in the URL.
	$families = array_map(
		static function ( $family ) {
			return str_replace( ' ', '+', $family );
		},
		$families
	);

	return 'https://fonts.googleapis.com/css2?family=' . implode( '&family=', $families ) . '&display=swap';
}

/**
 * Enqueues the fonts on the front end.
 */
function my_theme_fonts() {
	// null version: the URL already carries everything that identifies it,
	// and a ?ver= query on a third-party stylesheet only breaks caching.
	wp_enqueue_style( 'my-theme-fonts', my_theme_fonts_url(), array(), null );
}
add_action( 'wp_enqueue_scripts', 'my_theme_fonts' );

/**
 * Preconnects to both font hosts.
 *
 * The CSS comes from fonts.googleapis.com and the font files from
 * fonts.gstatic.com, which is a second connection and needs crossorigin.
 *
 * @param array  $urls          Hints for this relation.
 * @param string $relation_type The relation.
 * @return array
 */
function my_theme_fonts_preconnect( $urls, $relation_type ) {
	if ( 'preconnect' !== $relation_type ) {
		return $urls;
	}

	$urls[] = 'https://fonts.googleapis.com';
	$urls[] = array(
		'href'        => 'https://fonts.gstatic.com',
		'crossorigin' => 'anonymous',
	);

	return $urls;
}
add_filter( 'wp_resource_hints', 'my_theme_fonts_preconnect', 10, 2 );

/**
 * Loads the same fonts inside the editor.
 *
 * Without this the editor renders your content in a different typeface, so
 * line lengths and spacing decisions made there do not match the front end.
 */
function my_theme_fonts_editor() {
	add_editor_style( my_theme_fonts_url() );
}
add_action( 'after_setup_theme', 'my_theme_fonts_editor' );

Output is valid and updates as you type.

Load web fonts in WordPress with one request, a sensible font-display, the preconnect hints that actually help, and the editor covered as well as the front end.

How to use

  1. Decide where the fonts come from. Self-hosting removes a third-party connection, which is faster and avoids the privacy question several EU regulators have raised about Google Fonts.
  2. List only the weights you use. Each weight is another file, and a family with five weights costs more than the design gains.
  3. Leave display on swap. Text paints immediately in the fallback and swaps when the font arrives, instead of staying invisible.
  4. Keep preconnect on for the CDN. The stylesheet and the font files come from two different hosts, and the second hint needs crossorigin.
  5. Load the fonts in the editor too, or the typography you tune there will not match what visitors see.

Example

The URL builder, which is where the single-request part comes from:

function acme_fonts_url() {
	$families = array(
		'Inter:wght@400;600',
		'Playfair+Display:ital,wght@0,700;1,700',
	);

	return 'https://fonts.googleapis.com/css2?family=' . implode( '&family=', $families ) . '&display=swap';
}

Two wp_enqueue_style() calls for two families is two round trips before any styled text appears. One URL with two family parameters is one.

Pitfalls

  • A version string on an external stylesheet breaks caching for no benefit. Pass null as the version for a CDN URL.
  • font-display: block, the browser default for @font-face without the property, hides the text for up to three seconds. swap is almost always the better trade.
  • Preconnect to fonts.gstatic.com needs crossorigin; without it the browser opens a second connection anyway and the hint is wasted.
  • Preloading every font file delays everything else. Preload the one face used above the fold, or nothing.
  • The editor loads its own styles. Without add_editor_style() the content looks different there, which leads to line-length decisions that do not hold.
  • Spaces in a family name must be + in the URL. Playfair Display fails silently and you get the fallback.
  • Variable fonts are one file covering a weight range. [email protected] is often smaller than three static weights.
  • Google Fonts sets no cookies, but the request still sends the visitor’s IP to a third party, which is the basis of the German court rulings on it.

Compatibility

wp_enqueue_style(), wp_resource_hints and add_editor_style() have been stable since WordPress 4.6 and earlier. The css2 endpoint is the current Google Fonts API and supports variable font ranges and the display parameter. font-display is supported by every current browser. Block themes can also register fonts in theme.json, which is the native path when you are self-hosting. The generated code targets PHP 7.0 and up, and the tool runs entirely in your browser.

Frequently asked questions

Should I self-host?
Usually yes. It removes a connection, avoids the privacy question, and the files are small. Use theme.json font families if you are on a block theme.
Why is my text invisible for a moment?
That is font-display: block, the default. Set swap so the fallback paints immediately.
Does a version number matter here?
Not for a CDN URL. The URL already identifies the exact fonts, and adding ?ver= only defeats caching.
How many weights should I load?
As few as the design needs. Two per family is common; four is usually a design that has not been decided.
Do I need this in a block theme?
Not for self-hosted fonts registered in theme.json, which WordPress then loads for you. A CDN font still needs an enqueue.

From the people who built this tool

WP Adminify

The WordPress admin, rebuilt: a dashboard worth looking at, menu and column control, a real file manager and the login page your client sees.

See WP Adminify Free version on WordPress.org

Weekly drops

New tools, when there are new tools

One email when something worth using ships. No schedule to fill, so no filler.

Your address goes nowhere else, and one click unsubscribes.