{"id":6371,"date":"2026-08-18T20:44:17","date_gmt":"2026-08-18T20:44:17","guid":{"rendered":"https:\/\/developer.wordpress.org\/news\/?p=6371"},"modified":"2026-08-18T20:44:17","modified_gmt":"2026-08-18T20:44:17","slug":"hands-on-with-the-wordpress-7-1-icon-registration-api","status":"publish","type":"post","link":"https:\/\/developer.wordpress.org\/news\/2026\/08\/hands-on-with-the-wordpress-7-1-icon-registration-api\/","title":{"rendered":"Hands-on with the WordPress 7.1 Icon Registration API"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">When WordPress 7.0 launched the Icon block earlier this year, it was one of the most exciting moments I\u2019d felt in a while. But it was also a bit of a letdown because there was no public API for registering custom icons. The list of available icons for the block felt limiting for most creative uses of it.\u00a0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The good news: the public API is shipping with WordPress 7.1!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And it does nearly everything that I need for truly using it in projects, such as this restaurant menu idea I\u2019ve been tinkering with:<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6a86b3ac427a9&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6a86b3ac427a9\" class=\"wp-block-image alignwide size-full has-custom-border wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2048\" height=\"1141\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-menu.webp\" alt=\"WordPress post editor showing a restaurant menu in four groups of serving types. The Icon block is selected and its settings appear in the sidebar.\" class=\"has-border-color has-light-grey-2-border-color wp-image-6376\" style=\"border-width:1px\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-menu.webp 2048w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-menu-766x427.webp 766w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-menu-300x167.webp 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-menu-1024x570.webp 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-menu-1536x856.webp 1536w\" sizes=\"auto, (max-width: 2048px) 100vw, 2048px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In this tutorial, I\u2019ll walk you through some of the basics. Then you\u2019ll learn how to build a complete icon registration plugin from start to finish.<\/p>\n\n\n\n<div class=\"wp-block-group has-light-grey-2-background-color has-background is-layout-flow wp-block-group-is-layout-flow\" style=\"border-radius:2px;margin-top:var(--wp--preset--spacing--30);margin-bottom:var(--wp--preset--spacing--30);padding-top:var(--wp--preset--spacing--30);padding-right:var(--wp--preset--spacing--30);padding-bottom:var(--wp--preset--spacing--30);padding-left:var(--wp--preset--spacing--30)\">\n<p class=\"has-large-font-size wp-block-paragraph\" style=\"font-style:normal;font-weight:600;line-height:1\">Table of Contents<\/p>\n\n\n<nav aria-label=\"Table of Contents\" class=\"wp-block-table-of-contents\"><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#how-to-register-icons\">How to register icons<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#a-quick-overview-of-the-registration-functions\">A quick overview of the registration functions<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#an-example-collection-and-icon\">An example collection and icon<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#building-an-icon-registration-plugin\">Building an icon registration plugin<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#plugin-setup\">Plugin setup<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#bundling-icons-in-plugin\">Bundling icons in plugin<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#defining-the-icon-data-structure\">Defining the icon data structure<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#registering-the-icons-with-wordpress\">Registering the icons with WordPress<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#load-the-plugin-files-and-bootstrap\">Load the plugin files and bootstrap<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#outputting-icons\">Outputting icons<\/a><\/li><\/ol><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#the-missing-pieces-what-you-can-t-yet-do\">The missing pieces: what you can\u2019t yet do<\/a><ol><li><a class=\"wp-block-table-of-contents__entry\" href=\"#allowlist-of-elements\">Allowlist of elements<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#stroke-attribute-is-stripped\">Stroke attribute is stripped<\/a><\/li><li><a class=\"wp-block-table-of-contents__entry\" href=\"#no-editor-component-yet\">No editor component\u2026yet<\/a><\/li><\/ol><\/li><\/ol><\/nav><\/div>\n\n\n\n<h2 id=\"how-to-register-icons\" class=\"wp-block-heading\">How to register icons<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This tutorial covers the introductory layer of using the API, the relevant parts for most use cases. For a complete overview, read the <a href=\"https:\/\/make.wordpress.org\/core\/2026\/07\/24\/registering-and-rendering-svg-icons-in-wordpress-7-1\/\">Registering and rendering SVG icons in WordPress 7.1<\/a> developer note.<\/p>\n\n\n\n<h3 id=\"a-quick-overview-of-the-registration-functions\" class=\"wp-block-heading\">A quick overview of the registration functions<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When registering custom icons, you need to do two things:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Register a custom icon collection, or know the slug of an existing collection that you want to register icons for.<\/li>\n\n\n\n<li>Register your icons for a specific collection.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Most plugins and themes will require custom collections, which can be registered via the <code>wp_register_icon_collection()<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">wp_register_icon_collection( string $slug, array $args );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The function has two parameters that you must pass your arguments to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>$slug<\/code>:<\/strong> A unique key for your plugin for your collection. Ideally, this is prefixed with your plugin\/theme name (e.g., <code>plugin-collection<\/code>).<\/li>\n\n\n\n<li><strong><code>$args<\/code>:<\/strong> An array of arguments for the collection:\n<ul class=\"wp-block-list\">\n<li><strong><code>label<\/code>:<\/strong> A human-readable and internationalized label for the collection, which is used in the UI.<\/li>\n\n\n\n<li><strong><code>description<\/code>:<\/strong> An internationalized string describing the collection.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have a collection, you\u2019ll use <code>wp_register_icon()<\/code> to register individual icons:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">wp_register_icon( string $icon_name, array $icon_properties );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It also has two parameters:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>$icon_name<\/code>:<\/strong> A collection-namespaced identifier for an icon. It must start with a collection slug, followed by a <code>\/<\/code> (e.g., <code>plugin-collection\/icon-slug<\/code>).<\/li>\n\n\n\n<li><strong><code>$icon_properties<\/code>:<\/strong> An array of arguments to define the icon:\n<ul class=\"wp-block-list\">\n<li><strong><code>label<\/code>:<\/strong> An internationalized, human-readable label for the icon.<\/li>\n\n\n\n<li><strong><code>content<\/code>:<\/strong> The SVG markup for the icon (optional).<\/li>\n\n\n\n<li><strong><code>file_path<\/code>:<\/strong> A file path to the SVG markup for the icon (optional).<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Both <code>content<\/code> and <code>file_path<\/code> are optional parameters individually, but you must choose one or the other to actually register an icon.<\/p>\n\n\n\n<h3 id=\"an-example-collection-and-icon\" class=\"wp-block-heading\">An example collection and icon<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Later in this tutorial, you\u2019ll learn how to build a complete plugin with a restaurant icon collection. But let\u2019s start with a short example of utilizing the functions you just learned about.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When registering collections and icons, you should do so on the <code>init<\/code> hook. The following code, either added to a plugin file or theme\u2019s <code>functions.php<\/code>, will register a Cake icon under a Restaurant collection:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">add_action('init', 'devblog_restaurant_icons_register');\n\nfunction devblog_restaurant_icons_register(): void\n{\n\twp_register_icon_collection('devblog-restaurant', [\n\t\t'label'       =&gt; __('Restaurant', 'devblog-restaurant-icons'),\n\t\t'description' =&gt; __('Demo icons provided by the DevBlog Restaurant Icons plugin.', 'devblog-restaurant-icons')\n\t]);\n\n\twp_register_icon('devblog-restaurant\/cake', [\n\t\t'label'   =&gt; __('Cake', 'devblog-restaurant-icons'),\n\t\t'content' =&gt; '&lt;svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" height=\"24px\" viewBox=\"0 -960 960 960\" width=\"24px\" fill=\"#1f1f1f\"&gt;&lt;path d=\"M160-80q-17 0-28.5-11.5T120-120v-200q0-33 23.5-56.5T200-400v-160q0-33 23.5-56.5T280-640h160v-58q-18-12-29-29t-11-41q0-15 6-29.5t18-26.5l56-56 56 56q12 12 18 26.5t6 29.5q0 24-11 41t-29 29v58h160q33 0 56.5 23.5T760-560v160q33 0 56.5 23.5T840-320v200q0 17-11.5 28.5T800-80H160Zm120-320h400v-160H280v160Zm-80 240h560v-160H200v160Zm80-240h400-400Zm-80 240h560-560Zm560-240H200h560Z\"\/&gt;&lt;\/svg&gt;'\n\t]);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The code uses the <code>content<\/code> property above instead of <code>file_path<\/code>. For one-off examples, that\u2019s probably preferable because it shows the full code. You\u2019ll use the <code>file_path<\/code> property in the complete plugin example below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To test the registered collection and icon, insert the Icon block in the Post or Site Editor and click the <strong>Replace<\/strong> button in the block\u2019s toolbar. You will then see the <strong>Icon Library<\/strong> with the new <strong>Restaurant<\/strong> tab (i.e., collection):<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6a86b3ac452fa&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6a86b3ac452fa\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2048\" height=\"1135\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icons-tutorial-cake.jpg\" alt=\"WordPress post editor with an Icon Library modal overlaying the screen. The Restaurant icon collection tab is selected, showing a single Cake icon.\" class=\"wp-image-6374\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icons-tutorial-cake.jpg 2048w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icons-tutorial-cake-300x166.jpg 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icons-tutorial-cake-767x425.jpg 767w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icons-tutorial-cake-1024x568.jpg 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icons-tutorial-cake-1536x851.jpg 1536w\" sizes=\"auto, (max-width: 2048px) 100vw, 2048px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">After selecting the Cake icon, it will appear in the Icon block, which you can customize with the existing design tools.\u00a0<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The generated block markup looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"markup\" class=\"language-markup\">&lt;!-- wp:icon {\"icon\":\"devblog-restaurant\/cake\"} \/--&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see, both the collection and icon slug are included: <code>devblog-restaurant\/cake<\/code>. You can use this markup in templates or patterns, just like any other block.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As far as new development features go, this one is pretty straightforward. That\u2019s all you really need to know for most uses of the Icon Registration API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But let\u2019s dive into actually building a full project on top of this new API.<\/p>\n\n\n\n<h2 id=\"building-an-icon-registration-plugin\" class=\"wp-block-heading\">Building an icon registration plugin<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As shown earlier, I had an idea of using icons for a restaurant menu page. I knew that a few icons sprinkled throughout the design would give it a little more visual appeal to readers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So I\u2019ll walk you through how I built a plugin to add those icons. All the code is presented below in steps, but you can also study the complete code in its <a href=\"https:\/\/github.com\/wptrainingteam\/devblog-restaurant-icons\">GitHub repository<\/a>.<\/p>\n\n\n\n<h3 id=\"plugin-setup\" class=\"wp-block-heading\">Plugin setup<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">As with any other plugin, you must create a new folder under <code>wp-content\/plugins<\/code> and put a main plugin file inside it. You\u2019ll also need a couple of sub-folders for this project. Go ahead and create a plugin directory that is structured like this:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>devblog-restaurant-icons\/<\/code> \n<ul class=\"wp-block-list\">\n<li><code>public\/<\/code> \n<ul class=\"wp-block-list\">\n<li><code>icon\/<\/code> <\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><code>src\/<\/code> \n<ul class=\"wp-block-list\">\n<li><code>plugin.php<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Now add this code to <code>plugin.php<\/code> via your preferred code editor:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">&lt;?php\n\n\/**\n * Plugin Name:       DevBlog: Restaurant Icons\n * Plugin URI:        https:\/\/github.com\/wptrainingteam\/devblog-restaurant-icons\n * Description:       WordPress 7.1+ demo plugin for registering icon collections.\n * Version:           1.0.0\n * Requires at least: 7.1\n * Requires PHP:      8.1\n * Author:            Developer Blog\n * Author URI:        https:\/\/developer.wordpress.org\/news\n * License:           GPL-3.0-or-later\n * License URI:       https:\/\/www.gnu.org\/licenses\/gpl-3.0.html\n * Text Domain:       devblog-restaurant-icons\n *\/\n\ndeclare(strict_types=1);\n\nnamespace DevBlog\\RestaurantIcons;\n\n# Prevent direct access.\ndefined('ABSPATH') || exit;\n\n# Define the plugin constants.\nconst PLUGIN_DIR = __DIR__;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Most of this is pretty boilerplate stuff for creating plugins. But take note of the <code>PLUGIN_DIR<\/code> constant; you\u2019ll reference this later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Keep this file open in your editor. You\u2019ll also add more code to it later in this tutorial.<\/p>\n\n\n\n<div class=\"wp-block-wporg-notice is-info-notice\"><div class=\"wp-block-wporg-notice__icon\"><\/div><div class=\"wp-block-wporg-notice__content\"><p>I\u2019m building this as a plugin, but you can also register icons via a theme. The only difference will be where the icon SVG files are loaded from. You can skip the above code in a theme context.<br><\/p><\/div><\/div>\n\n\n\n<h3 id=\"bundling-icons-in-plugin\" class=\"wp-block-heading\">Bundling icons in plugin<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">For this project, I used multiple food-related icons from <a href=\"https:\/\/fonts.google.com\/icons\">Google\u2019s Material Icons library<\/a>. Feel free to pull them directly from there or get the list directly from <a href=\"https:\/\/github.com\/wptrainingteam\/devblog-restaurant-icons\/tree\/main\/public\/icon\">this project\u2019s GitHub repo folder<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you have the icons, put them into a <code>public\/icon<\/code> folder in your plugin. Your folder should be structured like this:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>icon\/<\/code>\n<ul class=\"wp-block-list\">\n<li><code>bakery.svg<\/code><\/li>\n\n\n\n<li><code>bento.svg<\/code><\/li>\n\n\n\n<li><code>breakfast.svg<\/code><\/li>\n\n\n\n<li><code>brunch.svg<\/code><\/li>\n\n\n\n<li><code>cake.svg<\/code><\/li>\n\n\n\n<li><code>dinner.svg<\/code><\/li>\n\n\n\n<li><code>kebab.svg<\/code><\/li>\n\n\n\n<li><code>lunch.svg<\/code><\/li>\n\n\n\n<li><code>ramen.svg<\/code><\/li>\n\n\n\n<li><code>restaurant.svg<\/code><\/li>\n\n\n\n<li><code>rice-bowl.svg<\/code><\/li>\n\n\n\n<li><code>soup-kitchen.svg<\/code><\/li>\n\n\n\n<li><code>tapas.svg<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h3 id=\"defining-the-icon-data-structure\" class=\"wp-block-heading\">Defining the icon data structure<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">There are many ways of representing the icon values in PHP. You could do a plain array, constants, generate an <a href=\"https:\/\/github.com\/WordPress\/WordPress\/blob\/7.1-branch\/wp-includes\/assets\/icon-library-manifest.php\">icon manifest like WordPress<\/a> itself (also an array), or just include a bunch of <code>wp_register_icon()<\/code> calls in your code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I\u2019m not a fan of those methods. PHP has had <a href=\"https:\/\/www.php.net\/manual\/en\/language.enumerations.overview.php\">enumerations<\/a> (enums) for five years (since 8.1). And, in this case, I prefer using a <a href=\"https:\/\/www.php.net\/manual\/en\/language.enumerations.backed.php\">string-backed enum<\/a> for compile-time safety and autocompletion. <em>Plus, no magic strings!<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In your plugin\u2019s <code>src<\/code> folder, create a new file named <code>Icon.php<\/code> and add this code to it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace DevBlog\\RestaurantIcons;\n\nenum Icon: string\n{\n\tcase Bakery      = 'bakery';\n\tcase Bento       = 'bento';\n\tcase Breakfast   = 'breakfast';\n\tcase Brunch      = 'brunch';\n\tcase Cake        = 'cake';\n\tcase Dinner      = 'dinner';\n\tcase Kebab       = 'kebab';\n\tcase Lunch       = 'lunch';\n\tcase Ramen       = 'ramen';\n\tcase Restaurant  = 'restaurant';\n\tcase RiceBowl    = 'rice-bowl';\n\tcase SoupKitchen = 'soup-kitchen';\n\tcase Tapas       = 'tapas';\n\n\t\/\/ Additional code for the enum goes here...\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">As you can see, each enum case represents one of the icon filenames (without the <code>.svg<\/code> extension) that you added in the previous step.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are also a couple of other pieces of data that you\u2019ll need for your icons:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The collection slug: <code>devblog-restaurant<\/code>.<\/li>\n\n\n\n<li>The absolute path to where the icons are stored in the plugin, which is the <code>PLUGIN_DIR<\/code> constant you added earlier + the relative path to <code>public\/icon<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Add these as constants inside the enum definition:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public const COLLECTION = 'devblog-restaurant';\n\nprivate const ICONS_PATH = PLUGIN_DIR . '\/public\/icon';<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The enum also needs to carry some behavior in this case. As covered earlier, there are three pieces of information that you need to register an icon: a name, label, and SVG. So each one of these things needs an equivalent method in the <code>Icon<\/code> enum for returning the data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, add a new <code>label()<\/code> method to the enum:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public function label(): string\n{\n\treturn match ($this) {\n\t\tself::Bakery      =&gt; __('Bakery', 'devblog-restaurant-icons'),\n\t\tself::Bento       =&gt; __('Bento', 'devblog-restaurant-icons'),\n\t\tself::Breakfast   =&gt; __('Breakfast', 'devblog-restaurant-icons'),\n\t\tself::Brunch      =&gt; __('Brunch', 'devblog-restaurant-icons'),\n\t\tself::Cake        =&gt; __('Cake', 'devblog-restaurant-icons'),\n\t\tself::Dinner      =&gt; __('Dinner', 'devblog-restaurant-icons'),\n\t\tself::Kebab       =&gt; __('Kebab', 'devblog-restaurant-icons'),\n\t\tself::Lunch       =&gt; __('Lunch', 'devblog-restaurant-icons'),\n\t\tself::Ramen       =&gt; __('Ramen', 'devblog-restaurant-icons'),\n\t\tself::Restaurant  =&gt; __('Restaurant', 'devblog-restaurant-icons'),\n\t\tself::RiceBowl    =&gt; __('Rice Bowl', 'devblog-restaurant-icons'),\n\t\tself::SoupKitchen =&gt; __('Soup Kitchen', 'devblog-restaurant-icons'),\n\t\tself::Tapas       =&gt; __('Tapas', 'devblog-restaurant-icons')\n\t};\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This uses <a href=\"https:\/\/www.php.net\/manual\/en\/control-structures.match.php\"><code>match()<\/code><\/a> to return the internationalized text label for the current enum case.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next, add a <code>handle()<\/code> method to the enum:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public function handle(): string\n{\n\treturn self::COLLECTION . '\/' . $this-&gt;value;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because an icon name needs to be namespaced with the collection name, we can just prepend it here. So any time you need a reference to an icon&#8217;s full handle\/name, you can call <code>Icon::CaseName-&gt;handle()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And, finally, you need a way to reference the absolute file path to the SVG, so add a <code>filePath()<\/code> method to the enum:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">public function filePath(): string\n{\n\treturn self::ICONS_PATH . '\/' . $this-&gt;value . '.svg';\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Note if building this in a theme, use <code>get_theme_file_path(\"public\/icon\/{$this-&gt;value}.svg\")<\/code>. There\u2019s no need for the <code>ICONS_PATH<\/code> constant at all.<\/p>\n\n\n\n<div class=\"wp-block-wporg-notice is-tip-notice\"><div class=\"wp-block-wporg-notice__icon\"><\/div><div class=\"wp-block-wporg-notice__content\"><p>Using a backed enum instead of an array gives <code>Icon<\/code> compile-time type safety and autocompletion. For example, <code>Icon::Cake<\/code> is a real, checkable type that PHP validates and IDEs can autocomplete, whereas an array key like <code>$icons['cake']<\/code> is just a string a typo and can silently break with no error until runtime.<\/p><p>The enum also lets behavior live next to the data. Methods like <code>label()<\/code>, <code>handle()<\/code>, and <code>filePath()<\/code> are defined once per case via <code>match<\/code>. Adding a new icon means adding one case and one line per method, and PHP&#8217;s match will warn you (via static analysis or a runtime <code>UnhandledMatchError<\/code>) if a case is left unhandled.<\/p><p>The enum makes the full set of valid icons closed, discoverable, and self-documenting, rather than an open-ended set of &#8220;magic strings&#8221; scattered across arrays.<\/p><\/div><\/div>\n\n\n\n<h3 id=\"registering-the-icons-with-wordpress\" class=\"wp-block-heading\">Registering the icons with WordPress<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Now that you have a type-safe, IDE-friendly representation of your icons in code, all that\u2019s left is letting WordPress know about it. You can do this with a small class with the singular responsibility of registering the icons.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create a new file under <code>src<\/code> with the name of <code>IconRegistrar.php<\/code>. Then add the following code to it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">&lt;?php\n\ndeclare(strict_types=1);\n\nnamespace DevBlog\\RestaurantIcons;\n\nfinal class IconRegistrar\n{\n\tpublic function boot(): void\n\t{\n\t\tadd_action('init', $this-&gt;register(...));\n\t}\n\n\tprivate function register(): void\n\t{\n\t\t\/\/ Register the collection.\n\t\twp_register_icon_collection(Icon::COLLECTION, [\n\t\t\t'label'       =&gt; __('Restaurant', 'devblog-restaurant-icons'),\n\t\t\t'description' =&gt; __('Demo icons provided by the DevBlog Restaurant Icons plugin.', 'devblog-restaurant-icons')\n\t\t]);\n\n\t\t\/\/ Register icons for the collection.\n\t\tforeach (Icon::cases() as $icon) {\n\t\t\twp_register_icon($icon-&gt;handle(), [\n\t\t\t\t'label'     =&gt; $icon-&gt;label(),\n\t\t\t\t'file_path' =&gt; $icon-&gt;filePath()\n\t\t\t]);\n\t\t}\n\t}\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This class has a <code>boot()<\/code> method that, when called, adds the <code>register()<\/code> action on the WordPress <code>init<\/code> hook. From there, it registers both the icon collection and the individual icons.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because you used a string-backed enum, you get the built-in static <a href=\"https:\/\/www.php.net\/manual\/en\/unitenum.cases.php\"><code>cases()<\/code><\/a> method for free, which returns an array of your icon cases. So the above code only needs to loop through each case and pass the result of each of the custom methods into <code>wp_register_icon()<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Also note that the code uses <code>Icon::COLLECTION<\/code> without needing to remember what that string was when registering the collection itself. <em>No typos to worry about!<\/em><\/p>\n\n\n\n<h3 id=\"load-the-plugin-files-and-bootstrap\" class=\"wp-block-heading\">Load the plugin files and bootstrap<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Last items: load the PHP files you just created and call the <code>IconRegistrar::boot()<\/code> method.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At the bottom of your <code>plugin.php<\/code> file, add this code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\"># Load classes manually. In production, use an autoloader.\nrequire_once PLUGIN_DIR . '\/src\/Icon.php';\nrequire_once PLUGIN_DIR . '\/src\/IconRegistrar.php';\n\n# Bootstrap services.\nadd_action('plugins_loaded', static function (): void {\n\t(new IconRegistrar())-&gt;boot();\n});<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now if you look at the <strong>Icon Library <span aria-hidden=\"true\" class=\"wp-exclude-emoji\">\u2192<\/span> Restaurant<\/strong> tab from earlier, you should see the full list of registered icons:<\/p>\n\n\n\n<figure data-wp-context=\"{&quot;imageId&quot;:&quot;6a86b3ac4711d&quot;}\" data-wp-interactive=\"core\/image\" data-wp-key=\"6a86b3ac4711d\" class=\"wp-block-image alignwide size-full wp-lightbox-container\"><img loading=\"lazy\" decoding=\"async\" width=\"2048\" height=\"1287\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-restaurant-icons.webp\" alt=\"WordPress post editor with an Icon Library modal overlaying the screen. The Restaurant icon collection tab is selected, showing various food-related icons in a grid.\" class=\"wp-image-6373\" srcset=\"https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-restaurant-icons.webp 2048w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-restaurant-icons-300x189.webp 300w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-restaurant-icons-767x482.webp 767w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-restaurant-icons-1024x644.webp 1024w, https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-tutorial-restaurant-icons-1536x965.webp 1536w\" sizes=\"auto, (max-width: 2048px) 100vw, 2048px\" \/><button\n\t\t\tclass=\"lightbox-trigger\"\n\t\t\ttype=\"button\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\tdata-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\"\n\t\t\tdata-wp-init=\"callbacks.initTriggerButton\"\n\t\t\tdata-wp-on--click=\"actions.showLightbox\"\n\t\t\tdata-wp-style--right=\"state.thisImage.buttonRight\"\n\t\t\tdata-wp-style--top=\"state.thisImage.buttonTop\"\n\t\t>\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\" \/>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure>\n\n\n\n<h3 id=\"outputting-icons\" class=\"wp-block-heading\">Outputting icons<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">As mentioned earlier in this article, you can reference registered icons via their names in the Icon block:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"markup\" class=\"language-markup\">&lt;!-- wp:icon {\"icon\":\"devblog-restaurant\/cake\"} \/--&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s often a poor development experience with larger collections because you must remember the exact string name. And there\u2019s no autocomplete if that string lives in a plain array somewhere else.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With the enum, it\u2019s easy to fix that without worrying about the exact name. To ensure that you get the correct Cake icon, reference it like so:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">&lt;!-- wp:icon {\"icon\":\"&lt;?= Icon::Cake-&gt;handle() ?&gt;\"} \/--&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If you\u2019re not in a block context (e.g., a classic theme or outputting as part of a plugin in some other way), you can use the <code>wp_get_icon()<\/code> function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"php\" class=\"language-php\">&lt;?= wp_get_icon(Icon::Cake-&gt;handle(), [\n\t'size' =&gt; 32\n]) ?&gt;<\/code><\/pre>\n\n\n\n<h2 id=\"the-missing-pieces-what-you-can-t-yet-do\" class=\"wp-block-heading\">The missing pieces: what you can\u2019t yet do<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Icon Registration API is super powerful at this point, allowing you to register icons for most needs. But there are still some limitations that will ship with WordPress 7.1. These are features that are actively being worked on for version 7.2 and beyond.<\/p>\n\n\n\n<h3 id=\"allowlist-of-elements\" class=\"wp-block-heading\">Allowlist of elements<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Currently, only <code>&lt;svg&gt;<\/code>, <code>&lt;path&gt;<\/code>, and <code>&lt;polygon&gt;<\/code> are allowed elements for an SVG icon. Everything else is stripped out. Part of this is because WordPress does not yet have a formal function for sanitizing SVGs. This is likely the biggest limiting factor for highly custom icons in 7.1.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/75550\">Work is ongoing in a PR<\/a> to address this limitation. It adds a full set of elements, such as <code>&lt;circle&gt;<\/code>, <code>&lt;rect&gt;<\/code>, and more while sanitizing them.<\/p>\n\n\n\n<h3 id=\"stroke-attribute-is-stripped\" class=\"wp-block-heading\">Stroke attribute is stripped<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Related to the above sanitization ticket, currently <code>stroke<\/code> is not an allowed attribute, so you cannot style SVGs with it. That means that you should stick to <code>fill<\/code>-based icons for defining your icon colors right now.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Additionally, <code>fill<\/code> is stripped from the outer <code>&lt;svg&gt;<\/code> but survives on <code>&lt;path&gt;<\/code> and <code>&lt;polygon&gt;<\/code>. The idea is that the outer SVG element should be styled through CSS (e.g., through the Icon block or custom CSS).<\/p>\n\n\n\n<h3 id=\"no-editor-component-yet\" class=\"wp-block-heading\">No editor component\u2026yet<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The Icon block was a great start, and a proper registration API made things much more powerful for extenders. But if there\u2019s something I\u2019m looking forward to even more it is using built-in components to add icons inside of other blocks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There\u2019s experimental work in this area, and here are some tickets to keep an eye on during the next development cycle:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/pull\/76787\">Move IconPickerModal into block-editor for reusability<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/61755\">Add more collapsed navigation icon options<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/73214\">Accordion Block: Block uses + sign rather than SVG for icon<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/WordPress\/gutenberg\/issues\/51019\">Details Block: allow the option to choose from a set of icons<\/a><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Of course, you are always welcome to build a custom component and perform the integration yourself in the meantime. There are read-only <a href=\"https:\/\/make.wordpress.org\/core\/2026\/07\/24\/registering-and-rendering-svg-icons-in-wordpress-7-1\/#rest-api\">REST API endpoints<\/a> available for icons.<\/p>\n\n\n\n<p class=\"has-text-align-right wp-block-paragraph\"><em>Props to <a href='https:\/\/profiles.wordpress.org\/bph\/' class='mention'><span class='mentions-prefix'>@<\/span>bph<\/a> and <a href='https:\/\/profiles.wordpress.org\/welcher\/' class='mention'><span class='mentions-prefix'>@<\/span>welcher<\/a> for reviewing this article.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>WordPress 7.1 introduces a public API for custom icons. Discover how to register collections and render SVGs easily with this complete, practical tutorial.<\/p>\n","protected":false},"author":20482,"featured_media":6379,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"{title}\n\n{excerpt}\n\n{url}","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"edge","default_image_id":0,"font":"","enabled":false},"version":2},"_wpas_customize_per_network":false,"jetpack_post_was_ever_published":false},"categories":[113,40,38],"tags":[10,230],"class_list":["post-6371","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-common-apis","category-plugins","category-themes","tag-extenders","tag-icons","mentions-bph","mentions-welcher"],"revision_note":"","jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/developer.wordpress.org\/news\/files\/2026\/08\/icon-registration-api.png","_links":{"self":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts\/6371","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/users\/20482"}],"replies":[{"embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/comments?post=6371"}],"version-history":[{"count":5,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts\/6371\/revisions"}],"predecessor-version":[{"id":6382,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/posts\/6371\/revisions\/6382"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/media\/6379"}],"wp:attachment":[{"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/media?parent=6371"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/categories?post=6371"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/developer.wordpress.org\/news\/wp-json\/wp\/v2\/tags?post=6371"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}