{"id":341442,"date":"2021-06-02T06:35:53","date_gmt":"2021-06-02T13:35:53","guid":{"rendered":"https:\/\/css-tricks.com\/?p=341442"},"modified":"2022-11-03T12:43:28","modified_gmt":"2022-11-03T19:43:28","slug":"a-crash-course-in-wordpress-block-filters","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/a-crash-course-in-wordpress-block-filters\/","title":{"rendered":"A Crash Course in WordPress Block Filters"},"content":{"rendered":"\n

Blocks in WordPress<\/a> are great. Drop some into the page, arrange them how you like, and you\u2019ve got a pretty sweet landing page with little effort. But what if the default blocks in WordPress need a little tweaking? Like, what if we could remove the alignment options in the Cover block settings? Or how about control sizing for the Button block?<\/p>\n\n\n\n

There are plenty of options when it comes to extending the functionality of core blocks in WordPress. We can add a custom CSS class to a block in the editor, add a custom style, or create a block variation<\/a>. But even those might not be enough to get what you need, and you find yourself needing to filter the core block to add or remove features, or building an entirely new block from scratch.<\/p>\n\n\n\n

I\u2019ll show you how to extend core blocks with filters and also touch on when it\u2019s best to build a custom block instead of extending a core one.<\/p>\n\n\n\n\n\n\n

A quick note on these examples<\/h3>\n\n\n

Before we dive in, I\u2019d like to note that code snippets in this article are taken out of context on purpose to focus on filters rather than build tools and file structure. If I included the full code for the filters, the article would be hard to follow. With that said, I understand that it\u2019s not obvious for someone who is just starting out where to put the snippets or how to run build scripts and make the whole thing work.<\/p>\n\n\n\n

To make things easier for you, I made a WordPress plugin with examples from this article available on my GitHub. Feel free to download it<\/a> and explore the file structure, dependencies and build scripts. There is a README<\/a> that will help you get started.<\/p>\n\n\n

Block filters in an nutshell<\/h3>\n\n\n

The concept of filters is not new to WordPress. Most of us are familiar with the add_filter()<\/a><\/code> function in PHP. It allows developers to modify various types of data using hooks<\/a>.<\/p>\n\n\n\n

A simple example of a PHP filter could look something like this:<\/p>\n\n\n\n

function filter_post_title( $title ){\n  return '<strong>' . $title . '<\/strong>';\n};\n\nadd_filter( 'the_title',  'filter_post_title' );<\/code><\/pre>\n\n\n\n

In this snippet, we create a function that receives a string representing a post title, then wrap it in a <strong><\/code> tag and return a modified title. We then use add_filter()<\/code> to tell WordPress to use that function on a post title.<\/p>\n\n\n\n

JavaScript filters work in a similar way. There is a JavaScript function called addFilter()<\/code> that lives in the wp.hooks<\/code> package and works almost like its PHP sibling. In its simplest form, a JavaScript filter looks something like this:<\/p>\n\n\n\n

function filterSomething(something) {\n  \/\/ Code for modifying something goes here.\n  return something;\n}\n\nwp.hooks.addFilter( 'hookName', 'namespace', filterSomething );<\/code><\/pre>\n\n\n\n

Looks pretty similar, right? One notable difference is addFilter()<\/code> has a namespace<\/code> as a second argument. As per the WordPress Handbook<\/a>, \u201cNamespace uniquely identifies a callback in the the form vendor\/plugin\/function<\/code>.\u201d However, examples in the handbook follow different patterns: plugin\/what-filter-does<\/code> or plugin\/component-name\/what-filter-does<\/code>. I usually follow the latter because it keeps the handles unique throughout the project.<\/p>\n\n\n\n

What makes JavaScript filters challenging to understand and use is the different nature of what they can filter. Some filter strings, some filter JavaScript objects, and others filter React components and require understanding the concept of Higher Order Components<\/a>.<\/p>\n\n\n\n

On top of that, you\u2019ll most likely need to use JSX which means you can\u2019t just drop the code into your theme or plugin and expect it to work. You need to transpile it to vanilla JavaScript that browsers understand. All that can be intimidating at the beginning, especially if you are coming from a PHP background and have limited knowledge of ES6, JSX, and React.<\/p>\n\n\n\n

But fear not! We have two examples that cover the basics of block filters to help you grasp the idea and feel comfortable working with JavaScript filters in WordPress. As a reminder, if writing this code for the Block Editor is new to you, explore the plugin<\/a> with examples from this article.<\/p>\n\n\n\n

Without any further ado, let\u2019s take a look at the first example.<\/p>\n\n\n

Removing the Cover block\u2019s alignment options<\/h3>\n\n\n

We\u2019re going to filter the core Cover block and remove the Left<\/em>, Center<\/em>, Right<\/em>, and Wide<\/em> alignment options from its block settings. This may be useful on projects where the Cover block is only used as a page hero, or a banner of some sort and does not need to be left- or right-aligned.<\/p>\n\n\n\n

We\u2019ll use the blocks.registerBlockType<\/a><\/code> filter. It receives the settings of the block and its name and must return a filtered settings object. Filtering settings allows us to update the supports<\/code> object that contains the array of available alignments. Let\u2019s do it step-by-step.<\/p>\n\n\n\n

We\u2019ll start by adding the filter that just logs the settings and the name of the block to the console, to see what we are working with:<\/p>\n\n\n\n

const { addFilter } = wp.hooks;\n\nfunction filterCoverBlockAlignments(settings, name) {\n  console.log({ settings, name });\n  return settings;\n}\n\naddFilter(\n  'blocks.registerBlockType',\n  'intro-to-filters\/cover-block\/alignment-settings',\n  filterCoverBlockAlignments,\n);<\/code><\/pre>\n\n\n\n

Let\u2019s break it down. The first line is a basic destructuring<\/a> of the wp.hooks<\/code> object. It allows us to write addFilter()<\/code> in the rest of the file, instead of wp.hooks.addFilter()<\/code>. This may seem redundant in this case, but it is useful when using multiple filters in the same file (as we\u2019ll get to in the next example).<\/p>\n\n\n\n

Next, we defined the filterCoverBlockAlignments()<\/code> function that does the filtering. For now, it only logs the settings object and the name of the block to the console and returns the settings as is.<\/p>\n\n\n\n

All filter functions receive data, and must return filtered data. Otherwise, the editor will break.<\/p>\n\n\n\n

And, lastly, we initiated the filter with addFilter()<\/code> function. We provided it with the name of the hook we are going to use, the filter namespace, and a function that does the filtering.<\/p>\n\n\n\n

If we\u2019ve done everything right, we should see a lot of messages in the console. But note that not all of them refer to the Cover block.<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

This is correct because the filter is applied to all<\/em> blocks rather than the specific one we want. To fix that, we need to make sure that we apply the filter only to the core\/cover<\/code> block:<\/p>\n\n\n\n

function filterCoverBlockAlignments(settings, name) {\n  if (name === 'core\/cover') {\n    console.log({ settings, name });\n  }\n  return settings;\n}<\/code><\/pre>\n\n\n\n

With that in place, we should see something like this now in the console:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

Don\u2019t worry if you see more log statements than Cover blocks on the page. I have yet to figure out why that\u2019s the case. If you happen to know why, please share in the comments!<\/em><\/p>\n\n\n\n

And here comes the fun part: the actual filtering. If you have built blocks from scratch before, then you know that alignment options are defined with Supports API<\/a>. Let me quickly remind you how it works\u2009\u2014\u2009we can either set it to true<\/code> to allow all alignments, like this:<\/p>\n\n\n\n

supports: {\n  align: true\n}<\/code><\/pre>\n\n\n\n

\u2026or provide an array of alignments to support. The snippet below does the same thing, as the one above:<\/p>\n\n\n\n

supports: {\n  align: [ 'left', 'right', 'center', 'wide', 'full' ]\n}<\/code><\/pre>\n\n\n\n

Now let\u2019s take a closer look at the settings<\/code> object from one of the console messages we have and see what we are dealing with:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

All we need to do is replace align: true<\/code> with align: ['full']<\/code> inside the supports<\/code> property. Here\u2019s how we can do it:<\/p>\n\n\n\n

function filterCoverBlockAlignments(settings, name) {\n  if (name === 'core\/cover') {\n    return assign({}, settings, {\n      supports: merge(settings.supports, {\n        align: ['full'],\n      }),\n    });\n  }\n  return settings;\n}<\/code><\/pre>\n\n\n\n

I\u2019d like to pause here to draw your attention to the assign<\/a><\/code> and merge<\/a><\/code> lodash methods. We use those to create and return a brand new object and make sure that the original settings<\/code> object remains intact. The filter will still work if we do something like this:<\/p>\n\n\n\n

\/* 👎 WRONG APPROACH! DO NOT COPY & PASTE! *\/\nsettings.supports.align = ['full'];\nreturn settings;<\/code><\/pre>\n\n\n\n

\u2026but that is an object mutation, which is considered a bad practice and should be avoided unless you know what you are doing. Zell Liew discusses why mutation can be scary<\/a> over at A List Apart.<\/p>\n\n\n\n

Going back to our example, there should now only be one alignment option in the block toolbar:<\/p>\n\n\n\n

\"\"<\/figure>\n\n\n\n

I removed the \u201ccenter\u201d alignment option because the alignment toolbar allows you to toggle the alignment \u201con\u201d and \u201coff.\u201d This means that Cover blocks now have default and \u201cFull width\u201d states.<\/p>\n\n\n\n

And here\u2019s the full snippet:<\/p>\n\n\n\n

const { addFilter } = wp.hooks;\nconst { assign, merge } = lodash;\n\nfunction filterCoverBlockAlignments(settings, name) {\n  if (name === 'core\/cover') {\n    return assign({}, settings, {\n      supports: merge(settings.supports, {\n        align: ['full'],\n      }),\n    });\n}\n  return settings;\n}\n\naddFilter(\n  'blocks.registerBlockType',\n  'intro-to-filters\/cover-block\/alignment-settings',\n  filterCoverBlockAlignments,\n);<\/code><\/pre>\n\n\n\n

This wasn\u2019t hard at all, right? You are now equipped with a basic understanding of how filters work with blocks. Let\u2019s level it up and take a look at a slightly more advanced example.<\/p>\n\n\n

Adding a size control to the Button block<\/h3>\n\n\n

Now let\u2019s add a size control to the core Button block. It will be a bit more advanced as we will need to make a few filters work together. The plan is to add a control that will allow the user to choose from three sizes for a button: Small,<\/em> Regular,<\/em> and Large<\/em>.<\/p>\n\n\n\n

\"\"
The goal is to get that new \u201cSize settings\u201d section up and running.<\/figcaption><\/figure>\n\n\n\n

It may seem complicated, but once we break it down, you\u2019ll see that it\u2019s actually pretty straightforward.<\/p>\n\n\n

1. Add a size attribute to the Button block<\/h4>\n\n\n

First thing we need to do is add an additional attribute that stores the size of the button. We\u2019ll use the already familiar blocks.registerBlockType<\/code> filter from the previous example:<\/p>\n\n\n\n

\/**\n * Add Size attribute to Button block\n *\n * @param  {Object} settings Original block settings\n * @param  {string} name     Block name\n * @return {Object}          Filtered block settings\n *\/\nfunction addAttributes(settings, name) {\n  if (name === 'core\/button') {\n    return assign({}, settings, {\n      attributes: merge(settings.attributes, {\n        size: {\n          type: 'string',\n          default: '',\n        },\n      }),\n    });\n  }\n  return settings;\n}\n\naddFilter(\n  'blocks.registerBlockType',\n  'intro-to-filters\/button-block\/add-attributes',\n  addAttributes,\n);<\/code><\/pre>\n\n\n\n

The difference between what we\u2019re doing here versus what we did earlier is that we\u2019re filtering attributes<\/code> rather than the supports<\/code> object. This snippet alone doesn\u2019t do much and you won\u2019t notice any difference in the editor, but having an attribute for the size is essential for the whole thing to work.<\/p>\n\n\n

2. Add the size control to the Button block<\/h4>\n\n\n

We\u2019re working with a new filter, editor.BlockEdit<\/a><\/code>. It allows us to modify the Inspector Controls panel (i.e. the settings panel on the right of the Block editor).<\/p>\n\n\n\n

\/**\n * Add Size control to Button block\n *\/\nconst addInspectorControl = createHigherOrderComponent((BlockEdit) => {\n  return (props) => {\n    const {\n      attributes: { size },\n      setAttributes,\n      name,\n    } = props;\n    if (name !== 'core\/button') {\n      return <BlockEdit {...props} \/>;\n    }\n    return (\n      <Fragment>\n        <BlockEdit {...props} \/>\n        <InspectorControls>\n          <PanelBody\n            title={__('Size settings', 'intro-to-filters')}\n            initialOpen={false}\n          >\n            <SelectControl\n              label={__('Size', 'intro-to-filters')}\n              value={size}\n              options={[\n                {\n                  label: __('Regular', 'intro-to-filters'),\n                  value: 'regular',\n                },\n                {\n                  label: __('Small', 'intro-to-filters'),\n                  value: 'small'\n                },\n                {\n                  label: __('Large', 'intro-to-filters'),\n                  value: 'large'\n                },\n              ]}\n              onChange={(value) => {\n                setAttributes({ size: value });\n              }}\n            \/>\n          <\/PanelBody>\n      <\/InspectorControls>\n      <\/Fragment>\n    );\n  };\n}, 'withInspectorControl');\n\naddFilter(\n  'editor.BlockEdit',\n  'intro-to-filters\/button-block\/add-inspector-controls',\n  addInspectorControl,\n);<\/code><\/pre>\n\n\n\n

This may look like a lot, but we\u2019ll break it down and see how straightforward it actually is.<\/p>\n\n\n\n

The first thing you may have noticed is the createHigherOrderComponent<\/a><\/code> construct. Unlike other filters in this example, editor.BlockEdit<\/a><\/code> receives a component<\/em> and must return a component<\/em>. That\u2019s why we need to use a Higher Order Component<\/a> pattern derived from React.<\/p>\n\n\n\n

In its purest form, the filter for adding controls looks something like this:<\/p>\n\n\n\n

const addInspectorControl = createHigherOrderComponent((BlockEdit) => {\n  return (props) => {\n    \/\/ Logic happens here.\n    return <BlockEdit {...props} \/>;\n  };\n}, 'withInspectorControl');<\/code><\/pre>\n\n\n\n

This will do nothing but allow you to inspect the <BlockEdit \/><\/code> component and its props<\/code> in the console. Hopefully the construct itself makes sense now, and we can keep breaking down the filter.<\/p>\n\n\n\n

The next part is destructuring the props:<\/p>\n\n\n\n

const {\n  attributes: { size },\n  setAttributes,\n  name,\n} = props;<\/code><\/pre>\n\n\n\n

This is done so we can use name<\/code>, setAttributes<\/code>, and size<\/code> in the scope of the filter, where:<\/p>\n\n\n\n