• Skip to primary navigation
  • Skip to main content

Nick Diego

  • About
  • Blog
  • Current Projects
  • Contact

Tutorials

Adding Custom Icons to the Icon Block

Nick Diego · May 2, 2022 ·

Since its release, the Icon Block for WordPress has included a basic library of icons sourced from @wordpress/icons. The block also allows you to insert custom SVG icons ad hoc, but this can be tedious especially if you’re inserting the same icon over and over.

I always intended for users to be able to add their own icons to the block itself. Over 1,000 active installs later, enabling custom icon libraries is by far the most common feature request.

Well, I am pleased to announce that this is now possible as of version 1.2.0 and this article will show you how. ⚡️

Getting Started

The Icons Block is a static block built almost entirely with JavaScript, more specifically, with React. Therefore, icons need to be added using JavaScript but don’t worry, it’s quite straightforward.

I am going to assume that you want to register a custom icon library in a WordPress theme. You could also adapt the following steps for a custom plugin.

In the function.php file of your theme, add the following:

function example_register_custom_icons() {
	wp_enqueue_script(
		'example-register-custom-icons',
		get_theme_file_uri( '/assets/js/register-custom-icons.js' ),
		array( 'wp-i18n', 'wp-hooks', 'wp-dom' ),
		wp_get_theme()->get( 'Version' ),
		true // Very important, otherwise the filter is called too early.
	);
}
add_action( 'enqueue_block_editor_assets', 'example_register_custom_icons' );

This should be familiar to those that have enqueued scripts and/or styles before. A couple of things to note:

  • The path to the JavaScript file can be anything. I generally organize my asset files in their own folder but it’s up to you.
  • The array defines the WordPress dependencies that we’ll need.
  • The true at the end is very important. It tells WordPress to load the file in the footer rather than the header. This file needs to be loaded after the Icon Block.

Finally, create the blank register-custom-icons.js file. Name the file whatever you like, just make sure it’s correctly referenced in the enqueue function above.

Now we’re ready to add a custom icon library.

Adding Custom Icons

Let’s start with the complete example implementation and then we’ll walk through each section. In the register-custom-icons.js file, add the following:

// Add custom icons to the Icon Block.
wp.domReady( () => {

	const { __ } = wp.i18n;
	const { addFilter } = wp.hooks;

	function addCustomIcons( icons ) {

		const customIcons = [
			{
				isDefault: true,
				name: 'bookmark',
				title: __( 'Bookmark', 'icon-block' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M15.5,4.25 L9,4.25 C7.48121644,4.25 6.25,5.48121644 6.25,7 L6.25,19.5 L6.25678483,19.6039817 C6.33269168,20.17682 7.02861659,20.4632798 7.48809353,20.0694425 L12.25,15.988 L17.0119065,20.0694425 C17.4984115,20.4864467 18.25,20.1407649 18.25,19.5 L18.25,7 C18.25,5.48120782 17.0188049,4.25 15.5,4.25 Z M15.5,5.75 L15.6278083,5.75645347 C16.2581415,5.82046462 16.75,6.3527795 16.75,7 L16.75,17.869 L12.7380935,14.4305575 L12.63989,14.3592262 C12.3667749,14.1927863 12.0115663,14.2165634 11.7619065,14.4305575 L7.75,17.868 L7.75,7 C7.75,6.30964356 8.30964356,5.75 9,5.75 L15.5,5.75 Z"></path></svg>',
				categories: [ 'category-one', 'category-two' ],
			},
			{
				name: 'cloud',
				title: __( 'Cloud', 'icon-block' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M12.0297869,6.25477399 C9.69310799,6.35627347 7.76028359,8.06517251 7.33640731,10.3195752 L7.33,10.357 L7.17131855,10.3970995 C5.4791682,10.8693286 4.25,12.4228808 4.25,14.25 C4.25,16.4591229 6.04086573,18.25 8.25,18.25 L16.25,18.25 C18.4591136,18.25 20.25,16.4591136 20.25,14.25 L20.2449807,14.0481533 C20.1583164,12.3092839 18.9581728,10.8518379 17.3286997,10.3970993 L17.169,10.357 L17.1636012,10.3195631 C16.726443,7.99471518 14.6845932,6.25 12.25,6.25 L12.0297869,6.25477399 Z M12.25,7.75 C14.1089517,7.75 15.641164,9.20368247 15.7444701,11.050879 C15.7655051,11.4270003 16.0622764,11.7291586 16.4379585,11.7569554 C17.7370378,11.8530748 18.75,12.9390845 18.75,14.25 C18.75,15.6306864 17.6306864,16.75 16.25,16.75 L8.25,16.75 C6.86929499,16.75 5.75,15.6306979 5.75,14.25 C5.75,12.9390902 6.76297645,11.8530752 8.06206149,11.7569554 C8.43774872,11.7291582 8.7345221,11.4269924 8.7555506,11.0508656 C8.85882307,9.20368024 10.391028,7.75 12.25,7.75 Z"></path></svg>',
				categories: [ 'category-one', 'category-two' ],
			},
			{
				name: 'heart',
				title: __( 'Heart', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M6.08714334,6.32932979 C3.96268923,8.06801188 3.63859886,11.0218225 5.33411073,13.1117301 L5.40693404,13.189484 L11.735394,19.050284 C12.0229467,19.3165872 12.4670552,19.3165879 12.7546087,19.0502856 L19.0831087,13.1894856 L19.1559241,13.1117421 C20.8618901,11.009037 20.5476544,8.03670134 18.3950317,6.32296945 L18.2021622,6.17656493 C16.3839665,4.86189789 14.0726771,4.99410522 12.3732475,6.33543793 L12.244,6.441 L12.1137885,6.33327648 C10.4051427,4.9868485 8.07169216,4.85041921 6.27728692,6.18112693 L6.08714334,6.32932979 Z M11.6855857,7.98275475 C11.9837742,8.31667652 12.5062017,8.31668548 12.8044017,7.98277393 C14.0606994,6.57602557 16.0037503,6.33654686 17.4607748,7.49649577 L17.6164202,7.627428 C18.9226437,8.78850986 19.1174439,10.620049 18.1123793,12.0085048 L18.021,12.127 L12.245,17.477 L6.468,12.127 L6.49897378,12.1666936 C5.32705305,10.7221704 5.54794474,8.70892882 7.0371596,7.49013516 C8.45843106,6.32695371 10.4260915,6.57232977 11.6855857,7.98275475 Z"></path></svg>',
				categories: [ 'category-one' ],
				keywords: [ 'love' ],
			},
			{
				name: 'mail',
				title: __( 'Mail', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M17.5,5.25 C19.0188049,5.25 20.25,6.48120782 20.25,8 L20.25,8 L20.25,16.5 C20.25,18.0188136 19.0188136,19.25 17.5,19.25 L17.5,19.25 L7,19.25 C5.48120782,19.25 4.25,18.0188049 4.25,16.5 L4.25,16.5 L4.25,8 C4.25,6.48121644 5.48121644,5.25 7,5.25 L7,5.25 Z M18.7288492,7.76980088 L12.7469304,13.0617474 C12.4915617,13.2876505 12.1206612,13.3102408 11.8421862,13.1295183 L11.7530696,13.0617474 L5.77115144,7.76980412 C5.75726181,7.84441732 5.75,7.92136061 5.75,8 L5.75,8 L5.75,16.5 C5.75,17.1903743 6.30963146,17.75 7,17.75 L7,17.75 L17.5,17.75 C18.1903864,17.75 18.75,17.1903864 18.75,16.5 L18.75,16.5 L18.75,8 C18.75,7.92135923 18.7427384,7.84441487 18.7288492,7.76980088 Z M17.5,6.75 L7,6.75 C6.96190904,6.75 6.92421603,6.75170376 6.886993,6.75503923 L12.249,11.499 L17.612,6.755 L17.5,6.75 Z"></path></svg>',
				categories: [ 'category-one' ],
			},
			{
				name: 'search',
				title: __( 'Search', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M11.25,4.25 C15.116007,4.25 18.25,7.3839999 18.25,11.25 C18.25,12.9128461 17.670205,14.4402654 16.7017462,15.6411267 L20.0303301,18.9696699 C20.3232233,19.2625631 20.3232233,19.7374369 20.0303301,20.0303301 C19.7640635,20.2965966 19.3473998,20.3208027 19.0537883,20.1029482 L18.9696699,20.0303301 L15.6411267,16.7017462 C14.4402654,17.670205 12.9128461,18.25 11.25,18.25 C7.3839999,18.25 4.25,15.116007 4.25,11.25 C4.25,7.38400644 7.38400644,4.25 11.25,4.25 Z M11.25,5.75 C8.21243356,5.75 5.75,8.21243356 5.75,11.25 C5.75,14.2875792 8.21242628,16.75 11.25,16.75 C14.2875864,16.75 16.75,14.2875864 16.75,11.25 C16.75,8.21242628 14.2875792,5.75 11.25,5.75 Z"></path></svg>',
				categories: [ 'category-two' ],
			},
			{
				isDefault: true,
				name: 'user',
				title: __( 'User', 'custom-icons' ),
				icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M11.800508,4.25 C9.59136901,4.25 7.800508,6.040861 7.800508,8.25 C7.800508,10.459139 9.59136901,12.25 11.800508,12.25 C14.009647,12.25 15.800508,10.459139 15.800508,8.25 C15.800508,6.040861 14.009647,4.25 11.800508,4.25 Z M11.800508,5.75 C13.1812199,5.75 14.300508,6.86928813 14.300508,8.25 C14.300508,9.63071187 13.1812199,10.75 11.800508,10.75 C10.4197961,10.75 9.300508,9.63071187 9.300508,8.25 C9.300508,6.86928813 10.4197961,5.75 11.800508,5.75 Z M11.800508,13.5 C8.04670918,13.5 5.68353852,14.9106243 4.49655562,17.1581489 C3.69148307,18.6824832 4.93973466,20.25 6.648008,20.25 L16.953008,20.25 C18.6612415,20.25 19.9094839,18.6825146 19.1045145,17.1581755 C17.917388,14.9105796 15.5542922,13.5 11.800508,13.5 Z M11.800508,15 C15.00061,15 16.8519251,16.1050874 17.7781285,17.8586756 C18.0005848,18.2799324 17.6262539,18.75 16.953008,18.75 L6.648008,18.75 C5.97474208,18.75 5.60043156,18.2799521 5.82293547,17.8586605 C6.74903109,16.1051199 8.60040067,15 11.800508,15 Z"></path></svg>',
				categories: [ 'category-two' ],
			},
		];

		const customIconCategories = [
			{
				name: 'category-one',
				title: __( 'Category One', 'example-theme' ),
			},
			{
				name: 'category-two',
				title: __( 'Category Two', 'example-theme' ),
			},
		];

		const customIconType = [
			{
				isDefault: true,
				type: 'example-icons',
				title: __( 'Example Icons', 'example-theme' ),
				icons: customIcons,
				categories: customIconCategories,
			},
		];

		const allIcons = [].concat( icons, customIconType );

		return allIcons;
	}

	addFilter(
		'iconBlock.icons',
		'example-theme/example-custom-icons',
		addCustomIcons
	);
} );

This code will add 6 new icons to the Icon Block. They will appear as their own icon type called “Example Icons”. We have also registered two icon categories.

Here are a few screenshots of what the finished product looks like:

Example Icons in the Icon Block quick inserter.
Example Icons in the Quick Inserter
Example Icons in the Icon Block inserter modal.
Example Icons in the Inserter modal

The Filter

Let’s simplify the code above. You’ll see that we are passing a callback function to the filter iconBlock.icons.

// Add custom icons to the Icon Block.
wp.domReady( () => {

	const { __ } = wp.i18n;
	const { addFilter } = wp.hooks;

	function addCustomIcons( icons ) {

		const customIconType = [ ... ];

		const allIcons = [].concat( icons, customIconType );

		return allIcons;
	}

	addFilter(
		'iconBlock.icons',
		'nick-diego/example-theme/example-custom-icons',
		addCustomIcons
	);
} );

The function and filter need to wrapped in wp.domReady(), which is one of the dependencies we included in the enqueue function. I have also simplified the other dependencies so they’re a bit easier to work with, but this is not required.

addFilter() is part of the Hooks API and is very similar to the PHP add_filter() function. The first parameter is the filter itself. The second is a unique namespace, which can be anything, but generally follows the format vendor/theme/function. The final parameter is the callback function.

The callback function addCustomIcons() accepts a single parameter icons that’s an array of all icons currently available in the Icon Block. By default, this would be the base icon library provided by @wordpress/icons.

Once an array of custom icons is defined in customIconType, which we’ll talk about next, I have concatenated the original icons array with the new array. You could also remove the WordPress icons from the plugin entirely and just use your custom icons. To do so, return customIconType instead of allIcons in the code above.

Icon Type

Custom icons are added to the Icon Block by defining an icon type. In the following example code, I have defined the type “Example Icons”. You can register multiple types.

const customIconType = [
	{
		isDefault: false,
		type: 'example-icons',
		title: __( 'Example Icons', 'example-theme' ),
		icons: customIcons,
		categories: customIconCategories,
	},
];

An icon type is defined by five parameters. All are required unless specified:

  • isDefault – Determines if icons in this type should appear first in the Quick Inserter and Inserter Modal. (optional)
  • type – A slug to define the icon type.
  • title – A descriptive title for the icon type.
  • icons – An array of all custom icons in the icon type.
  • categories – An array of all icon categories in the icon type. (optional)

Icons

Next, we need to add the icons themselves.

const customIcons = [
	{ ... },
	{
		isDefault: false,
		name: 'heart',
		title: __( 'Heart', 'custom-icons' ),
		icon: '<svg width="24px" height="24px" viewBox="0 0 24 24"><path d="M6.08714334,6.32932979 C3.96268923,8.06801188 3.63859886,11.0218225 5.33411073,13.1117301 L5.40693404,13.189484 L11.735394,19.050284 C12.0229467,19.3165872 12.4670552,19.3165879 12.7546087,19.0502856 L19.0831087,13.1894856 L19.1559241,13.1117421 C20.8618901,11.009037 20.5476544,8.03670134 18.3950317,6.32296945 L18.2021622,6.17656493 C16.3839665,4.86189789 14.0726771,4.99410522 12.3732475,6.33543793 L12.244,6.441 L12.1137885,6.33327648 C10.4051427,4.9868485 8.07169216,4.85041921 6.27728692,6.18112693 L6.08714334,6.32932979 Z M11.6855857,7.98275475 C11.9837742,8.31667652 12.5062017,8.31668548 12.8044017,7.98277393 C14.0606994,6.57602557 16.0037503,6.33654686 17.4607748,7.49649577 L17.6164202,7.627428 C18.9226437,8.78850986 19.1174439,10.620049 18.1123793,12.0085048 L18.021,12.127 L12.245,17.477 L6.468,12.127 L6.49897378,12.1666936 C5.32705305,10.7221704 5.54794474,8.70892882 7.0371596,7.49013516 C8.45843106,6.32695371 10.4260915,6.57232977 11.6855857,7.98275475 Z"></path></svg>',
		categories: [ 'category-one' ],
		keywords: [ 'love' ],
	},
	{ ... },
];

Icons in the customIcons array are defined by six parameters. All are required unless specified:

  • isDefault – Determines if the icon should be displayed first in the list of available icons. (optional)
  • name – A slug to define the icon.
  • title – A descriptive title for the icon.
  • icon – A string of the SVG code that makes up the icon.
  • categories – An array of category names that the icon belongs to. (optional)
  • keywords – An array of keywords that help define the icon. Keywords are used when the user is searching for icons. (optional)

Categories

Finally, we need to define icon categories. Categories are optional, but they are a great way to organize your custom icons.

const customIconCategories = [
	{
		name: 'category-one',
		title: __( 'Category One', 'example-theme' ),
	},
	{
		name: 'category-two',
		title: __( 'Category Two', 'example-theme' ),
	},
];

Categories in the customIconCategories array are defined by two parameters. All are required:

  • name – A slug to define the icon category.
  • title – A descriptive title for the icon category.

That’s it, you can now add your own icon library to the Icon Block!

Closing Thoughts

There are a few tips I wanted to share regarding custom SVG icons, especially if you are designing them yourself.

  1. To take full advantage of the Icon Block and its internal controls, I recommended converting all SVGs to outlines if possible.
  2. Flatten your SVGs before exporting them from your design application. This will generally make the string smaller.
  3. After exporting from your design application, remove all unnecessary markup from the SVG code. This will make the string smaller and easier to work with.
  4. Unless you need SVGs to be a specific color, remove fill and other color values. Icon color can be controlled from within the block.

To follow Icon Block development, star the repository on GitHub or follow on Twitter. If you have any questions or a feature that you would like to see, let me know.

Tutorials Icon Block

Disable Responsive Columns in Gutenberg and Other Tips

Nick Diego · July 7, 2021 ·

If you have been working with Gutenberg, especially the new Full Site Editor, you may have found yourself a bit frustrated with the Columns block. 

The block generally works great on desktops. Yes, I would like to be able to control the space between columns in the Block Editor, but that’s a minor gripe. However, things do start to fall apart on mobile devices. 

Applying Different Breakpoints 

The Columns block natively collapses down to a single column at 600px. This makes some initial sense. In most layouts, the content in each column would become very “squished” if the columns were not mobile responsive. 

But what if I want to change the breakpoint at which the columns collapse?

In one application, I am using the Columns block to support a sidebar-content design. I need the columns to collapse at 782px to match my desired layout. 600px is way too small. 

Luckily we can make this happen with some simple CSS.

/* Extend responsive column styling to 782px (Default is 600px) */
@media (max-width: 782px) {

    .wp-block-columns .wp-block-column {
        flex-basis: 100% !important;
    }

	.wp-block-column:nth-child(2n) {
		margin-left: 0;
	}
}

The .wp-block-column:nth-child(2n) line defines the space between each column. WordPress sets this to zero automatically at 600px. We need this to be zero starting at 782px.

Reversing Columns on Mobile

What about the order of the columns when they become stacked on mobile devices? 

In my sidebar-content layout, I want to the sidebar to appear after the main content on mobile devices. By default the Columns block collapses the individual columns from left to right. In many cases this would be the desired implementation, but for my theme’s layout, I need the reverse. 

Here I chose to implement this as a special CSS class. Then I can apply the class column-reverse-on-mobile as needed to the relevant Columns blocks. If you wanted to get fancy, you could also create a block style.

/* Reverse the order of columns on mobile devices */
@media (max-width: 782px) {

    .wp-block-columns.column-reverse-on-mobile {
        flex-direction: column-reverse;
    }
}

Disabling Responsive Columns on Mobile

What if you don’t want the columns to be mobile responsive at all? 

There are many use cases for this, notably the header on a website. I am using a Columns block for a header in one of my themes. The left column has the Site Logo block and the right column has a Navigation block. By default, it looks like this on mobile. 

Note here that I am using the new “Enable responsive menu” functionality on the Navigation block. 

Columns are mobile responsive and break layout.

You can see that the menu icon is below the logo because the columns are stacked on top of each other. 

Instead, I want it to look like this.

Columns are not mobile responsive and the layout looks great.

Disabling responsive columns can be achieved using the following CSS. Again, I have chosen to use a CSS class so I can easily apply it to Columns blocks at will.

/* Disable responsive columns on mobile */
@media (max-width: 782px) {

    .wp-block-columns.is-not-stacked-on-mobile {
		flex-wrap: nowrap;
    }

    /* Available space should be divided equally amongst columns. */
	.wp-block-columns.is-not-stacked-on-mobile > .wp-block-column {
		flex-basis: 0 !important;
		flex-grow: 1;
    }

    /* When columns are in a single row, add space before all except the first. */
    .wp-block-columns.is-not-stacked-on-mobile > .wp-block-column:not(:first-child) {
        margin-left: 2em;
    }

    /* Columns with an explicitly-assigned width should maintain their`flex-basis` width and not grow. */
    .wp-block-columns.is-not-stacked-on-mobile > .wp-block-column[style*="flex-basis"] {
        flex-basis: auto !important;
        flex-grow: 0;
    }
}

This CSS was actually adapted from a GitHub ticket for the Gutenberg project. I am excited to say that @andrewserong has implemented a toggle directly in the Columns block settings which will handle this functionality for you.

At time of writing, it looks like this enhancement will soon be added to the Gutenberg version 11.1. However it will likely be some time until it makes its way into WordPress core. 

It’s important to note that there may be some quirks in the code. As mentioned in the GitHub ticket, more optimal approaches will be explored in the future. But in the meantime, take advantage of the CSS above to stop your columns from being responsive.

I am sure there are other solutions out there and additional Columns block-related tricks you can employ. Let me know if you have come across any good ones yourself!

I am going to try and document more Gutenberg and Full Site Editing tips as I discover them. Follow me on Twitter to stay in the loop.

Tutorials Gutenberg

Displaying Custom Taxonomies in the WordPress Site Editor

Nick Diego · February 26, 2021 ·

I am working on building a new site for one of my projects. On that site, I have a “Knowledge Base” section that utilizes a custom post type called article. Articles have a number of custom taxonomies, one of which is article-category. These “Article Categories” are different from normal post categories and I need a way to display them in the WordPress Site Editor as a block.

Skip to solution

The problem

If you are using the Gutenberg plugin, you have access to all the latest tools for Full Site Editing (FSE). The Site Editor is the focal point of FSE. While everything is still very much in beta mode, I am using block-based themes (required for FSE) on my personal sites. This is forcing me learn more about the project as a whole and get ready for the introduction of FSE in WordPress version 5.8 later this year.

Gutenberg currently includes a very handy block called “Post Categories”, which displays the current post’s categories. See the end of this post for an example.

The Post Categories block in the block inserter.

The name of the block should be changed in the future, because it actually allows you to display any taxonomy. But, there is a catch, which is why I am writing this article.

The block contains virtually no settings and there is no way to select from all available taxonomies directly from the block interface. Perhaps this will be added in the future, but for theme developers, there is actually a very elegant solution, despite it not being documented (yet).

Block variations.

Until this point, I had never explored block variations and there is very little official documentation on the subject. But as I was perusing the code on GitHub for the Post Terms block, I discovered the variations.js file, which looks like this:

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';

const variations = [
	{
		name: 'category',
		title: __( 'Post Categories' ),
		icon: 'category',
		isDefault: true,
		attributes: { term: 'category' },
	},
];

export default variations;

The solution

While I had a vague knowledge of block variations, a quick Google search on block variations led me to an article on CSS Tricks and an article by Rich Tabor.

Since it seemed like the Post Terms block accepts variations and has a term attribute, I threw together the following code in a register-block-variations.js file in my theme. Note that the title should really be translatable for completeness.

/**
 * Register the Article Categories variation for the Post Terms block.
 */
wp.domReady( () => {

    wp.blocks.registerBlockVariation(
        'core/post-terms',
        {
            name: 'article-category',
    		title: 'Article Categories',
    		icon: 'category',
    		isDefault: false,
    		attributes: { term: 'article-category' },
        },
    );

} );

Finally, we just need to enqueue the file. Something like this:

/**
 * Enqueue block variations script.
 */
function namespace_enqueue_block_variations() {

	wp_enqueue_script( 'namespace-enqueue-block-variations', get_theme_file_uri( '/register-block-variations.js' ), array( 'wp-blocks', 'wp-dom', 'wp-edit-post' ), wp_get_theme()->get( 'Version' ), true );
}
add_action( 'enqueue_block_editor_assets', 'namespace_enqueue_block_variations' );

Now when I search in the block inserter, the “Article Categories” variation appears! 🙌

The Article Categories block variation in the block inserter.

This solution can then be repeated for any number of custom taxonomies that you have on your theme.

Ultimately, it would be helpful if the “Post Terms” block just included settings that allowed you to select the terms (i.e. taxonomies) that you wish to display. But block variations work very well and having a separate “block” that appears in the inserter for each taxonomy is nice when working on complicated layouts in FSE.

I hope that was helpful, and let me know if you have discovered anything interesting about FSE. This is all one big learning experience! 😉

Full Site Editing, Tutorials Taxonomies

A primer on WordPress SlotFill technology

Nick Diego · January 3, 2021 ·

What is SlotFill and how can you use it in your projects? Well, lets start at the beginning of my SlotFill exploration.

A common business model in the WordPress ecosystem is to provide a free plugin (or theme), generally distributed on WordPress.org, and then offer premium functionality that users can purchase.

Two real-world examples are WooCommerce and Easy Digital Downloads. Both companies offer a base plugin on WordPress.org and then sell a variety of extensions.

If you follow this approach, at some point in the development process you will probably find yourself asking, “How do I integrate my premium functionality with the base plugin?”

At least I did.

One of the best qualities of the WordPress is it’s extensibility. Whether you want to extend WordPress itself, or your own project, the most common method is to use WordPress hooks. Using our plugin example, hooks allow you to register custom actions and filters throughout the plugin in important spots. Premium functionality can then be “hooked” into your project giving users access to these paid features.

Plugin development aside, if you have ever tinkered with a WordPress theme you have undoubtedly come into contact with hooks. Actions and filters are everywhere in WordPress. Take one look at a theme’s functions.php file and you will find numerous instances of add_action() and add_filter().

Traditional WordPress hooks, however, are based in PHP. With WordPress quickly moving to React due to the Block Editor, many new projects will likely be built almost entirely in JavaScript. This does not mean that PHP hooks will be going away, but it does require new technology to provide similar extensibility when working with JavaScript. I ran into this problem myself with my Block Visibility plugin.

Now there is actually an analogous hook API for JavaScript, and I will be using it in this article’s example, but I want to showcase how you can use SlotFill to accomplish many of your extensibility needs in JavaScript.

So what is SlotFill?

SlotFill Overview

Based on my research, SlotFill was implemented into WordPress by the team over at 10up with Ryan Welcher spearheading most of the development. Ryan put together a great overview of SlotFill back in 2019. He describes SlotFill as basically a modern take on the traditional (PHP) WordPress hook architecture.

Slot and Fill are a pair of components which enable developers to render elsewhere in a React element tree, a pattern often referred to as “portal” rendering. It is a pattern for component extensibility, where a single Slot may be occupied by an indeterminate number of Fills elsewhere in the application.

Ryan Welcher – Extending Gutenberg With SlotFill and Filters

There are three components that makeup SlotFill. In addition to Slot and Fill, the application needs to be wrapped in the SlotFillProvider component which essentially enables everything. Below is an adapted example from the Block Editor Handbook.

import { SlotFillProvider, Slot, Fill, Panel, PanelBody } from '@wordpress/components';
 
const ExampleSlotComponent = ( props ) => {
 
    return (
        <SlotFillProvider>
            <Panel header="Panel with slot">
                <PanelBody>
                    <Slot name="ExampleSlot"/>
                </PanelBody>
            </Panel>
            <Fill name="ExampleSlot">
                Panel body
            </Fill>
        </SlotFillProvider>
    );
};

Even though the content in the Fill component is written outside of the <Panel> component, it will actually be rendered inside of <PanelBody>.

It’s like magic! ????‍♂️

In all seriousness though, I encourage you go read Ryan’s article and watch the presentation that he did at the 2019 JavaScript for WordPress conference, The Gutenberg SlotFill System. These resources provide an in-depth overview of how SlotFill works and how it has been implemented into the new Block Editor (Gutenberg). With SlotFill currently being used extensively throughout the Block Editor, it will likely become a primary tool for maintaining WordPress extensibility in the future.

That said, since this technology is included with WordPress core, third-party developers (like myself) can make use of this pattern in their own projects!

SlotFill in Practice

In my current project, I have a custom settings page that is written in React and lives in my base plugin. I then have a premium add-on plugin that provides additional features and settings. When add-on is activated, I would like these new individual settings to appear on the main settings page provided by the base plugin. Specifically, I want a license activation box to appear at the top of the page, and the other premium settings to display at the bottom.

The current settings page before SlotFill is introduced

I will be using SlotFill, so the settings page needs to have a Slot at the top and bottom. The license activation box would be placed in a Fill for the top Slot. The other settings would be placed in a Fill for the bottom Slot.

First we need reconfigure the main base plugin to make use of SlotFill.

Main Plugin

Let’s assume that the JSX markup for the settings panel looks something like the following.

const SettingsComponent = ( props ) => {

    return (
        <div className="settings-container">
            <div className="setting-item">
                // Existing setting in main plugin
            </div>
            <div className="setting-item">
                // Existing setting in main plugin
            </div>
        </div>
    );
}

Let’s add the SlotFill markup. You start by wrapping everything in the SlotFillProvider component and then add the various Slot components. In this example, I want a slot at the beginning of the settings container and one at the end.

import { SlotFillProvider, Slot } from '@wordpress/components';

const SettingsComponent = ( props ) => {

    return (        
        <SlotFillProvider>
            <div className="settings-container">
                <Slot name="PluginSettingsTop">
                <div className="setting-item">
                    // Existing setting in main plugin
                </div>
                <div className="setting-item">
                    // Existing setting in main plugin
                </div>
                <Slot name="PluginSettingsBottom">
            </div>
        </SlotFillProvider>
    );
}

Finally, we need to add a component that we can filter and will allow us to add our Fill components from the premium add-on. Think of this component as a “door-way” into the main plugin. Also, if you need properties from the main plugin to be available in the add-on, you can simply pass them to this component.

In the code block below, I have created the AdditionalSettings component using the withFilters() function. You can learn more about this function in the Block Editor Handbook. In general, it provides filtering capabilities to a component that can then be controlled by an external hook name, in this case myExamplePlugin.Settings.

Note, that I am using a bit of a trick here. Instead of passing a component to withFilters() as the documentation indicates, I am just passing ( props ) => <></>. All the content we need is provided by the premium add-on, so we are just filtering an empty JSX fragment. Other implementations may be different, so review the documentation in the Block Editor Handbook if you are not familiar with withFilters().

I then have added the AdditionalSettings component inside of the SlotFillProvider and have included the properties that I want to pass.

import { withFilters, SlotFillProvider, Slot } from '@wordpress/components';

const SettingsComponent = ( props ) => {

    const AdditionalSettings = withFilters(
            'myExamplePlugin.Settings'
        )( ( props ) => <></> );

    return (
        <SlotFillProvider>
            <AdditionalSettings
                exampleProp={ exampleProp }
                { ...props }
            />
            <div className="settings-container">
                <Slot name="SettingsTop">
                <div className="setting-item">
                    // Existing setting in main plugin
                </div>
                <div className="setting-item">
                    // Existing setting in main plugin
                </div>
                <Slot name="SettingsBottom">
            </div>
        </SlotFillProvider>
    );
}

All the setup in the main plugin is now complete. Lets switching over to the premium add-on plugin.

Premium Add-on

The setup for the premium add-on is quite simple. All we need to do is use the addFilter( 'hook', 'namespace', 'callback' ) function to add the necessary Fill components. We just need to make sure we are using the correct hook, which is myExamplePlugin.Settings in this example.

Note that the namespace can be anything you like, but is required unlike in PHP. More information on addFilter() can be found in the Block Editor Handbook.

import { addFilter } from '@wordpress/hooks';
import { Fill } from '@wordpress/components';

function premiumSettings() {
    return ( props ) => (
        <Fill name="SettingsTop">
            <div className="license-activation-box">
                // The markup for the license activation box
            </div>
        </Fill>
        <Fill name="SettingsBottom">
            <div className="premium-setting-item">
                // Additional premium setting that should appear at the bottom of the settings container
            </div>
        </Fill>
    );
}

addFilter(
    'myExamplePlugin.Settings',
    'my-example-plugin/settings',
    premiumSettings
);

Putting it all together

That’s all we need to do! Now when we load the settings page, the premium functionality in the Fill components will be “slotted” into the correct Slot components in the base plugin. ????

SlotFill Example
The settings page after premium functionality has been added via SlotFill

Summary

Of course, in this example I have abstracted away from how to actually build a React-powered setting page and there is a ton of nuance concerning SlotFill that I am likely overlooking, or have yet to learn myself. That said, I hope I have illustrated the potential of this technology and intrigued you enough to want to learn more and/or try using SlotFill in your own projects.

Before ending, I want to share a couple of interesting discoveries and a list of all the SlotFill references I have been using. Surprisingly, there are not very many.

Discoveries

The SlotFillProvider component is not needed when you are adding custom Slots in the block settings sidebar.

Under the hood, the entire settings sidebar in the Block Editor is wrapped in a SlotFillProvider component. This allows for developers to add controls to the InspectorControls component, add editor plugins, etc. This was the entire impetus for SlotFill as Ryan outlines in his article and presentation.

Therefore, if you are adding your own Slot in a component that “lives” anywhere in the settings sidebar, you don’t have to worry about wrapping your code in the SlotFillProvider. That is already taken care of for you!

You cannot programmatically set the display order of multiple Fills in a single Slot.

If you have multiple Fill components that all target a single Slot, there is no way to stay “Fill number 1 should be displayed first, then Fill number 2”. This is unlike the hook API where you can specify a priority. Setting an add_action() or add_filter() with the priority 5, for example, will always be preformed before others with a priority greater than 5.

Currently, Fill components are ordered based on how they were written in the code, and how that code was loaded to the page. Whichever Fill slots into the Slot first will be displayed first. In the future there will hopefully be a way to specify a priority on each Fill. That said, using the example above, I only really see this being an issue if you had multiple add-on plugins that were all providing Fill components for the same Slot.

References

  • Official SlotFill entry in WordPress Block Editor Handbook
  • “Extending Gutenberg With SlotFill and Filters” – Original 10up article by Ryan Welcher
  • “The Gutenberg SlotFill System” – Video presentation by Ryan Welcher
  • GitHub resource for “The Gutenberg SlotFill System” presentation
  • Gutenberg SlotFill and Filter demos – GitHub repo by 10up

This article will get updated over time with new discoveries and insights. If you have any of your own, please share them in the comments!

Tutorials Block Editor, Gutenberg, SlotFill

Activating a license in React for EDD Software Licensing while avoiding CORS issues

Nick Diego · November 24, 2020 ·

If you have ever written a WordPress plugin or theme with the ultimate goal of selling this content, you most likely stumbled upon Easy Digital Downloads, or EDD for short. EDD is my marketplace software of choice and was purpose built to sell, well, digital downloads, making it perfect for WordPress developers.

EDD has a number of add-ons, including the Software Licensing add-on. This allows you to sell a product and provide a license key to the purchaser. In your plugin or theme, the user can then activate the license, which will enable automatic downloads and any additional functionality that you have developed.

The process of activating a license is pretty straight forward and EDD provides a number of examples, and even a sample plugin and theme, which shows you exactly how to get this all setup.

So what’s the problem?

JavaScript (React)

JavaScript (React) is becoming increasingly important for WordPress development, especially with the introduction of the new Block Editor and full-site editing on the horizon. Therefore, in my Block Visibility project, I wanted to build an admin settings page in React as opposed to the normal method using PHP. This meant that the license activation would take place in a React component, which was exciting since it provided the opportunity to improve the activation UI. I was never that happy with previous implementations I had done in PHP.

EDD conveniently had an example of how to activate licenses in pure JavaScript. This would be a quick port to React, so I figured the whole process would be quite simple.

At this point, it is important to mention that I am a self-taught developer, and there are some holes in my “education”. Seasoned developers may have been able to spot the impending issue right away, but hopefully many of you will find my trials and tribulations insightful.

In very general terms, when a user activates a license, they are sending a “request” back to the website that they purchased the product from, which will return a “response” indicating if the activation was successful or not. Enter CORS:

Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.

Wikipedia

Making a resource request between two websites can inherently have security implications. You don’t want just anyone requesting license information, which includes some customer data, from your online store. If you try activating a license using the JavaScript example that EDD provides, there is a chance that you will get an error similar to the one below:

CORS Policy Error

My store website did not have a HTTP response header set which resulted in this CORS error. There are of course ways to add a header to your server, Mozilla has a nice writeup about this, but none of the options seemed reasonable from a convenience or security perspective. I am not trying to create a public API and I don’t want my project to rely on a specific server configuration to work properly.

The saving grace here is that this issue really only impacts client-side requests, i.e. requests made in JavaScript. If the license activation request is made in PHP, or server-side, you are able to bypass the CORS policy. This is by design and there is a ton more information about CORS that you can find with a quick Google search. Too much to even try and dissect in this post.

The solution: Ajax

Making the license request directly from JavaScript was a no-go for my setup, so I needed to head back to PHP. However, I still wanted all my license activation UI in React. The solution is to write a PHP function that handles the request and uses Ajax to invoke this function from React. Ajax is used pretty frequently in WordPress, but generally with jQuery. This was my first time making an Ajax call in React.

Enqueueing Scripts

To get started, we need to append some variables to our project’s JavaScript file using the wp_add_inline_script function. The two variables we need to add are the URL for the admin-ajax.php and a security token, or a nonce. The implementation would look something like the following.

function demo_enqueue_project_scripts() {

	...

	wp_enqueue_script(
		'demo-setting-scripts',
		plugin_dir_url( __FILE__ ) . 'dist/demo-settings.js',
		array( 'wp-api' ), // Array of dependancies (Optional depending on your needs)
		$version, // Script version (Optional depending on your needs)
		true // Enqueue the file in the footer (Optional depending on your needs)
	);

	// Variables for running the license controls ajax request.
	$variables = 'const demoAjaxUrl = ' . wp_json_encode( admin_url( 'admin-ajax.php' ) ) . ';';
	$variables .= 'const demoLicenseControlsNonce = ' . wp_json_encode( wp_create_nonce( 'demo-license-controls-nonce' ) ) . ';';

	wp_add_inline_script(
		'demo-setting-scripts',
		$variables,
		'before'
	);

	...
}
add_action( 'admin_enqueue_scripts', 'demo_enqueue_project_scripts' );

PHP Function

Next we need to write the PHP function that will actually handle the license activation/deactivation request to the store website. Before I dive into the particulars, here is what this function should roughly look like. I do recommend reviewing the EDD Software Licensing API documentation. The function below is based on the sample plugin that is provided by EDD when you purchase the Software Licensing add-on.

function demo_license_controls() {

	check_ajax_referer( 'demo-license-controls-nonce' );

	$api_url    = 'https://www.demostore.com';
	$item_name  = 'Demo Plugin';
	$item_id    = 1234;
	$edd_action = sanitize_text_field( $_POST['edd_action'] );
        $license    = sanitize_text_field( $_POST['license'] );

	// Data to send to the API
	$api_params = array(
		'edd_action' => $edd_action,
		'license'    => $license,
		'item_name'  => urlencode( $item_name ),
		'item_id'    => BVP_ITEM_ID,
		'url'        => home_url()
	);

	// Call the API
	$response = wp_remote_post(
		$api_url,
		array(
			'timeout'   => 15,
			'sslverify' => false,
			'body'      => $api_params
		)
	);

	// Make sure there are no errors
	if (
		is_wp_error( $api_response ) ||
		200 !== intval( wp_remote_retrieve_response_code( $api_response ) )
	)
		wp_die( __( 'There has been an error retrieving the license information from the server, perhaps you are no longer connected to the internet. Please contact support.', 'demo-text-domain' ) );
	}

	// There are no errors, so retrieve the response
	echo wp_remote_retrieve_body( $response );

	wp_die();
}
add_action( 'wp_ajax_demo_license_controls', 'demo_license_controls' );

The first thing I am doing in this function is validating the nonce. We do not want to proceed with this API call if there is no valid security token.

The $api_url variable is the URL for the website with EDD Software Licensing running on it, i.e. the product store. The $item_name and $item_id refer to the product the user is attempting to activate. Refer to the documentation on where to find the id. Note that these variables are hardcoded for demonstration purposes, but you will likely come up with a more elegant solution.

Next I am setting the $edd_action and $license key. These two variables come from the UI in React and I will talk more about them later. Variables from JavaScript are passed to PHP via Ajax and are retrieved using the $_POST method in this example.

With all the variables set, we make the API call using wp_remote_post() which preforms an HTTP request using the POST method. Assuming there are no errors, we then retrieve the response.

Note that there are other ways in PHP to make an HTTP request, but using all of the WordPress-specific functions makes debugging much easier, especially is_wp_error() and wp_die().

The most important part of the code above is the last line. There are two actions that we could use here. wp_ajax_(your action name) only works for users that are logged-in to your website. wp_ajax_nopriv_(your action name) is for users that are not logged-in. Both can be used in tandem, but since license activation takes place in the admin area of the website, we just need wp_ajax_(your action name).

The (your action name) part of the action, or demo_license_controls in this example, will be used in the Ajax call on the React side.

React Function

Finally, we need to make the Ajax call in React where the license activation UI “lives”. First, a quick recap of the data flow we are attempting to create and what a user will actually be doing.

  1. The user will enter their license key into a text field and then click an “Activate” button.
  2. The license key value will then be passed to our PHP function via Ajax.
  3. The PHP function will make an API call to the store website to activate the license, if it is activable.
  4. A response will be sent back with a success or failure message.
  5. We will then parse the response and display a message to the user about whether the activation was successful or not.

Steps 1 and 5 are outside the scope of this post, but are pretty straight forward. WordPress does have a number of built-in components, such as text inputs and buttons, that I encourage you to check out. I am leveraging them in my projects and the Developer Handbook provides a good overview of each one.

Step 2 is where we need to make our Ajax call. There are many different Ajax libraries that you could use, but I chose the Fetch API since it is built-in to all modern browsers and I did not want to include another library in my project. The function should look something like the following:

// Valid actions are activate_license, deactivate_license, get_version, check_license
async function licenseControls( license = '', edd_action = 'activate_license' ) {
	const response = await fetch( demoAjaxUrl, {
		method: 'POST',
		credentials: 'same-origin',
		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
		body: new URLSearchParams( {
			_ajax_nonce: demoLicenseControlsNonce,
			action: 'demo_license_controls',
			edd_action: edd_action,
			license: license,
		} )
	} );
	return response.json();
}

The function accepts two parameters, the license key and the EDD action. This function can actually be used to activate a license, deactivate a license, check the status of a license, or get the current version number of the product, all based on the edd_action parameter.

You will note that we are fetching from the demoAjaxUrl, which we enqueued with our scripts earlier in this post. We are also using the nonce, demoLicenseControlsNonce, as one of the parameters.

Next, we have the action parameter set to demo_license_controls, which we set when adding our PHP function. And finally, we are passing the license and edd_action.

Now when the user clicks the “Activate” button, your React app will call the above function, which will return a JSON response. If the activation is successful, the response will look something like:

{
    "success": true,
    "license": "valid",
    "item_id": 1234
    "item_name": "Demo Plugin",
    "license_limit": 0,
    "site_count": 2,
    "expires": "2021-06-30 23:59:59",
    "activations_left": "unlimited",
    "checksum": "<MD$ Checksum>",
    "payment_id": 12345,
    "customer_name": "John Doe",
    "customer_email": "john@sample.org",
    "price_id": "2"
}

You can then use this data to provide a success notice to the user. EDD has examples of other response options if, for example, the activation fails or you are using a different EDD action.

Summary

The topics covered in this post are just the tip of the iceberg, but I hope it helps you overcome the CORS issue that I ran into when using the Software Licensing add-on for EDD. To simplify everything I wrote above into three bullet points:

  1. Create your license activation UI in React.
  2. Handle the actual license activation in a PHP function.
  3. Invoke the PHP function using Ajax from your React app.

That’s it, mission complete! ????

Tutorials CORS, Easy Digital Downloads, React

  • Go to page 1
  • Go to page 2
  • Go to Next Page »

© 2022 Nick Diego · Built with Frost & Block Visibility

Twitter · Instagram · LinkedIn