In this post, I’ll show you ways you can use PHP to filter the block editor’s settings beyond simply editing theme.json. We’ll break down how the block editor settings layers are resolved and go over some practical ways you can adjust settings using PHP.
Block editor settings are primarily configured using the active theme’s theme.json file and most of the time, updating that file is the best way to do so. However, there are a few special cases where you may want to make some modifications to the settings without actually modifying theme.json.
A few upfront examples of when you might reach for this:
- You’re using a Hybrid theme that doesn’t have a theme.json file
- You’re creating a plugin that alters or restricts editor options for users
- You want to include custom design values from your plugin as available design tokens in the block editor
Before we get into those use cases, let’s go over the state of themes in WordPress right now.
The state of WordPress themes
WordPress themes come in four possible types:
| Classic | Hybrid | Block | |
|---|---|---|---|
| Block Editor | ✅ | ✅ | ✅ |
| Site Editor | ❌ | ❌ | ✅ |
| PHP Templates | ✅ | ✅ | ✅ |
| HTML Templates | ❌ | ❌ | ✅ |
| theme.json | ❌ | Optional | Required |
An important takeaway here is that not all themes that support the block editor may have a theme.json file. This is especially possible if the theme was refactored when the block editor was newer and hasn’t been worked on much since.
You may be wondering: why not just add theme.json to the theme then? While this can be a viable strategy, adding theme.json to your theme causes other side effects that you may not be aware of and could conflict with your theme’s styles in some way (more on this below).
Additionally, if you’re writing plugin code and you need to alter some setting or other in the block editor (i.e. restricting allowed blocks, adding more color palettes, etc) you need some way to do so without modifying the code of the active theme (for shame).
How block editor settings are resolved
When WordPress builds the configuration for the editor, it merges data from four origins:
- Core defaults: WordPress ships its own default theme.json
- Blocks: per-block data registered by individual blocks
- Theme: data from your theme’s theme.json file
- User: anything saved through Global Styles (i.e. stored in the database)
That merged configuration is what powers things like color presets, font size dropdowns, spacing controls, layout widths, and block-level styles.
When your theme has a theme.json file, WordPress flips on a bunch of related behavior:
- Layout flow CSS. The
.is-layout-flowand.is-layout-constrainedrules, thewp-container-*classes that handle nested block spacing, and the wide/full alignment system all kick in. This adds rules for margin/gap that wouldn’t have existed before. - The
--wp--style--block-gapcustom property. This is what consistent spacing between blocks rides on. - Default core block styles behave slightly differently than they do in the no-theme.json case.
- The classic-theme-styles stylesheet (a fallback WordPress loads for themes without theme.json) is skipped.
- Preset CSS is generated for whatever colors, font sizes, and spacing values are defined.
- Global Styles UI becomes available to the extent the theme supports it.
There’s a function called wp_theme_has_theme_json() that gates a lot of this behavior, and here’s the catch worth memorizing: it does a literal file_exists() check on theme.json in your theme directory. It does not care whether you’ve added settings through PHP filters, so you can filter those settings without accepting the side effects that come with a theme.json file.
Approach 1: add_theme_support()
This is the original API from before theme.json existed, and it still works today. You register support for specific editor features from the after_setup_theme hook:
<?php
add_action( 'after_setup_theme', 'mytheme_editor_setup' );
function mytheme_editor_setup() {
add_theme_support( 'editor-color-palette', array(
array(
'name' => __( 'Primary', 'mytheme' ),
'slug' => 'primary',
'color' => '#1a1a1a',
),
array(
'name' => __( 'Accent', 'mytheme' ),
'slug' => 'accent',
'color' => '#e63946',
),
) );
add_theme_support( 'editor-font-sizes', array(
array(
'name' => __( 'Small', 'mytheme' ),
'slug' => 'small',
'size' => 14,
),
array(
'name' => __( 'Large', 'mytheme' ),
'slug' => 'large',
'size' => 22,
),
) );
add_theme_support( 'align-wide' );
add_theme_support( 'responsive-embeds' );
add_theme_support( 'custom-line-height' );
add_theme_support( 'custom-spacing' );
}The full list of supported features is long, but the ones you’ll reach for most: editor-color-palette, editor-font-sizes, editor-gradient-presets, disable-custom-colors, disable-custom-font-sizes, disable-custom-gradients, align-wide, responsive-embeds, custom-line-height, custom-units, custom-spacing, and appearance-tools (which bundles a bunch of options on at once).
One thing worth knowing: when a classic theme adds something like a custom color palette through theme support, WordPress disables the default core palette unless you also add default-color-palette theme support. Same pattern applies to gradients (default-gradient-presets), font sizes (default-font-sizes), and spacing sizes (default-spacing-sizes). If you suddenly lose access to all of WordPress’s built-in colors after adding your own, that’s why.
When to use it: small sites with simple preset needs and not much else. If you’re just adding a brand color palette and a couple of font sizes but theme.json is off the table, this is the lowest-friction option.
Where it falls short: anything block-specific, anything that would live in styles (rather than settings), spacing scales, layout widths, custom CSS, or per-block configuration. There isn’t feature parity with the full theme.json spec or the other options below.
Approach 2: The block_editor_settings_all Filter
This filter runs after WordPress has merged the four theme.json layers and built the final settings array that gets passed to the block editor. You’re filtering the editor’s internal settings shape, which is similar to theme.json data but not identical (camelCase keys, some keys remapped, plus a bunch of editor-only settings that don’t exist in theme.json at all).
<?php
add_filter( 'block_editor_settings_all', 'mytheme_block_editor_settings', 10, 2 );
function mytheme_block_editor_settings( $settings, $context ) {
// Override the color palette.
$settings['colors'] = array(
array(
'name' => __( 'Primary', 'mytheme' ),
'slug' => 'primary',
'color' => '#1a1a1a',
),
array(
'name' => __( 'Accent', 'mytheme' ),
'slug' => 'accent',
'color' => '#e63946',
),
);
// Limit allowed blocks in the editor.
$settings['allowedBlockTypes'] = array(
'core/paragraph',
'core/heading',
'core/image',
'core/list',
'core/quote',
);
return $settings;
}The second parameter ($context) is a WP_Block_Editor_Context object that tells you what kind of editor is being loaded (post editor, site editor, etc.) and which post is being edited. That’s the superpower of this filter: you can branch on context in ways the other approaches can’t.
When to use it:
- Editor-only behavior that doesn’t really belong in theme.json (allowed block types, block locking, custom inserter configuration)
- Context-aware overrides (different settings on different post types, for example)
- Last-mile changes that need to win over everything else
Where it falls short: this filter operates on already-merged data, so it’s the wrong place to define your baseline configuration. If you use it to set your color palette and then a plugin uses one of the theme.json filters, you’ll have a confusing time figuring out which value is applying correctly. Use it for editor concerns, not as a substitute for the actual theme.json data layer.
Approach 3: The wp_theme_json_data_* Filters
This is the modern recommended approach, introduced in WordPress 6.1. These filters are what you should reach for most of the time. There are four of them, one for each data layer:
wp_theme_json_data_default: the WordPress core defaultswp_theme_json_data_blocks: per-block data registered by blockswp_theme_json_data_theme: data that would normally come from your theme’s theme.json filewp_theme_json_data_user: Global Styles data (highest priority)
Each one receives a WP_Theme_JSON_Data instance. You call update_with() on it, passing an array that uses the same shape as a real theme.json file. WordPress merges your data into the appropriate layer.
For the “we don’t have a theme.json file but want to act like we do” case, wp_theme_json_data_theme is almost always the right pick:
add_filter( 'wp_theme_json_data_theme', 'mytheme_filter_theme_json' );
function mytheme_filter_theme_json( $theme_json ) {
$new_data = array(
'version' => 3,
'settings' => array(
'color' => array(
'palette' => array(
array(
'slug' => 'primary',
'color' => '#1a1a1a',
'name' => __( 'Primary', 'mytheme' ),
),
array(
'slug' => 'accent',
'color' => '#e63946',
'name' => __( 'Accent', 'mytheme' ),
),
),
'custom' => false,
),
'layout' => array(
'contentSize' => '720px',
'wideSize' => '1200px',
),
'spacing' => array(
'units' => array( 'px', 'rem', 'em', '%' ),
'spacingScale' => array(
'operator' => '*',
'increment' => 1.5,
'steps' => 7,
'mediumStep' => 1.5,
'unit' => 'rem',
),
),
),
'styles' => array(
'blocks' => array(
'core/button' => array(
'border' => array(
'radius' => '0',
),
),
),
),
);
return $theme_json->update_with( $new_data );
}A few things worth calling out from that example:
- The
versionkey is required. As of WordPress 6.6 the current schema version is3. If you forget this, the merge silently fails. - The structure is the same shape you’d use in an actual theme.json file. If you’ve ever written one, this is going to feel familiar.
- You can include both
settingsandstyles, which is what makes this approach more capable thanadd_theme_support(). Block-specific styles, spacing scales, custom CSS, layout widths, all of it is available.
When to use it: anytime you want PHP to do the work of a theme.json file. Color palettes, font sizes, spacing scales, layout widths, per-block styles, blockGap configuration, custom CSS, all of it.
Where it falls short: this populates the theme data layer, but it does not flip wp_theme_has_theme_json() to true. Some of the layout flow behavior and other file-presence side effects mentioned earlier still won’t activate. This is not a substitute for an actual theme.json file and certain settings will not work as expected when the file doesn’t exist.
Picking the Right Approach
If you’re building from scratch today and you have flexibility in the matter, the most maintainable answer is to just add a theme.json file. The PHP filters are the right call when you can’t (or shouldn’t) modify the theme directly, like when you’re adding configuration from a plugin or a must-use file in an environment where the theme is locked down.
With that constraint in mind, here’s a breakdown of when each of the above techniques might make sense to use:
- Just need a brand color palette and a couple of font sizes?
add_theme_support()is fine. - Need block-level styles, spacing scales, layout widths, or anything that looks like real theme.json content?
wp_theme_json_data_theme. - Need editor-only overrides (allowed blocks, locking, context-specific tweaks)?
block_editor_settings_all.
Most real projects in these situations end up using a combination. add_theme_support() for the basics, wp_theme_json_data_theme for anything block-specific or styles-related, and block_editor_settings_all when you need context-aware editor tweaks. Many projects are able to get by with just add_theme_support (especially if they’re locking down options in the editor).
Further Reading
- How to modify theme.json data using server-side filters (WordPress Developer Blog)
- Global Settings & Styles (Block Editor Handbook)
wp_theme_json_data_theme(WordPress Developer Reference)block_editor_settings_all(WordPress Developer Reference).wp_theme_has_theme_json()(WordPress Developer Reference)- 15 ways to curate the WordPress editing experience (WordPress Developer Blog)
Happy coding!