How to filter theme.json with PHP

In this lesson

WordPress version 6.1 introduced four new PHP filters for theme.json. You can use the filters to override both the WordPress default theme.json, theme settings, and user settings.

Estimated reading time: 3 minutes

Prerequisites: Global Styles & theme.json.

Last updated

How it works

Here is how it works: WordPress loads a default theme.json file (source). This file has all the default settings including colors and gradients. WordPress merges this data with settings from other sources, in the following order:

  • The block itself (block supports)
  • The theme.json file in the active theme (and parent theme if applicable)
  • User settings: The options in the Styles sidebar in the Site Editor

Just like there are four sources (the announcement post calls them layers), there are four filters:

This is important because it means that we can:

  • Override default settings, but permit theme and user settings.
  • Override deault and theme settings from our plugin, but permit user settings.

Basic usage

When you override theme.json settings it is important to know that there are some formatting differences between the PHP filter and theme.json. In theme.json you use both JavaScript objects and arrays, while in this PHP code example, the color palette object is a nested array.

Create a new PHP function with the theme.json data as a parameter. Hook the function to the filter:

function prefix_filter_theme_json_theme( $theme_json ){
	
}
add_filter( 'wp_theme_json_data_theme', 'prefix_filter_theme_json_theme' );

Add your changes in the form of an array. You must include the theme.json version, even if you are not changing it:

function filter_theme_json_theme( $theme_json ){
	$new_data = array(
		'version'  => 3,
		'settings' => array(
			'color' => array(
				'palette' => array(
					array(
						'slug'  => 'base',
						'color' => 'white',
						'name'  => __( 'Base', 'text-domain' ),
					),
				),
			),
		),
	);

	return $theme_json->update_with( $new_data );
}
add_filter( 'wp_theme_json_data_theme', 'filter_theme_json_theme' );

Return the changes using update_with which is a part of the WP_Theme_JSON_Data PHP Class:

https://developer.wordpress.org/reference/classes/wp_theme_json_data/
https://developer.wordpress.org/reference/classes/wp_theme_json_data/update_with/

Differences compared to block_editor_settings_all

So what makes the new filters different from the block_editor_settings_all filter? They have slightly different use cases:

  • When you use block_editor_settings_all, WordPress has already merged the data from the four sources into one.
  • You can use block_editor_settings_all to filter other things than theme.json settings, like locking.
  • The wp_theme_json_data filters work directly with the WP_Theme_JSON_Data class.
  • The formatting is different, the wp_theme_json_data filters uses a format that is very close to what you use in your theme.json file.

Limited support in classic themes

I have mentioned that WordPress uses the default theme.json even in classic themes.
But my testing shows that there are limits to how well the filter works for classic themes, and you can’t use it to enable features that are not supported.

For example, the drop cap typography feature is enabled by default, and you can use the filter to disable it. You can use the filter as an alternative to creating a hybrid theme with its own theme.json file.

function filter_theme_json_theme( $theme_json ){
	$new_data = array(
		'version'  => 3,
		'settings' => array(
			'typography' => array(
				'dropCap' => false
			),
		),
	);

	return $theme_json->update_with( $new_data );
}
add_filter( 'wp_theme_json_data_default', 'filter_theme_json_theme' );

Similarly, you can enable the link color setting, but if you want to change the default color of the links, the color only works on the front of the website.

function filter_theme_json_theme( $theme_json ){
	$new_data = array(
		'version'  => 3,
		'settings' => array(
			'color' => array(
				'link' => true
			)
		),
		"styles" => array(
			'blocks' => array(
				'core/post-title' => array(
					'elements'    => array(
						'link'    => array(
							'color' => array(
								'text' => 'red'
							)
						)
					)
				)
			)
		)
	);

	return $theme_json->update_with( $new_data );
}
add_filter( 'wp_theme_json_data_default', 'filter_theme_json_theme' );

You can’t use it to enable useRootPaddingAwareAlignments or add default spacing:
On a positive note it also doesn’t break anything.

function filter_theme_json_theme( $theme_json ){
	$new_data = array(
		'version'  => 3,
		'settings' => array(
			"useRootPaddingAwareAlignments" => true,
		),
		"styles" => array(
			'spacing' => array(
				'padding' => array(
					'top'    => '1rem',
					'right'  => '1rem',
					'bottom' => '1rem',
					'left'   => '1rem'
				)
			)
		)
	);

	return $theme_json->update_with( $new_data );
}
add_filter( 'wp_theme_json_data_default', 'filter_theme_json_theme' );

Are the filters a replacement for theme.json?

One of the things I wanted to explore was if it is possible to completely replace theme.json with this filter. The answer is sort of, but there are internal checks for the theme.json file that enables or disables features based on if the file exist.

When the file exists, WordPress enables the following:

  • Layout support for blocks. The layout in this case is the “Inner blocks use content width” and related options for container blocks.
  • Theme support overrides. For example if a theme has a color palette both in theme.json and add_theme_support('editor-color-palette').

If the file does not exist:

  • WordPress loads a set of default CSS rules for layout and margin, as well as some block styles. This is essentially a fallback intended for classic themes.
  • WordPress re-adds the inner div to the group block markup.

If you want to learn more please review the usage of wp_theme_has_theme_json() function in WordPress 6.2.

Resources