releases.shpreview

SVG icons now a public API: register, render, and REST access

v7.1

From the original release noteView original ↗
  1. Icon collections
    1. Creating a collection
    2. Removing a collection
  2. Icons
    1. Adding icons
    2. Removing an icon
  3. Icon block enhancement
  4. Rendering an icon in PHP
  5. Styling icons
    1. Styling React icons from @wordpress/icons
    2. Styling the SVG returned by wp_get_icon()
  6. REST API

WordPress 7.0 added a built-in set of SVG icons that the block editor and the core/icon block can use. In 7.1, this becomes a proper, public API: you can now add your own icons, group them, render them on the server, and read them over the REST API.

Icons are registered in one place and can then be used in several: the editor, the REST API, and your own PHP. This note covers the pieces you’ll work with:

  • Creating and removing icon collections.
  • Adding and removing individual icons.
  • Browsing icons by collection in the Icon block’s picker.
  • Rendering an icon in PHP with wp_get_icon().
  • The REST API endpoints for collections and icons.

Icon collections

Every icon belongs to a collection. A collection is just a named group of icons, and its name becomes a prefix: that’s what makes core/plus different from my-plugin/plus. This lets icons from different sources — WordPress, a plugin, or a third-party icon set — live side by side without clashing.

WordPress registers one collection by default, called core, with its own bundled icons.

Creating a collection

An icon can only be added to a collection that already exists, so start by registering the collection with wp_register_icon_collection().

function my_plugin_register_icon_collection() {
	wp_register_icon_collection(
		'my-plugin',
		array(
			'label'       => __( 'My Plugin Icons', 'my-plugin' ),
			'description' => __( 'Icons provided by My Plugin.', 'my-plugin' ),
		)
	);
}
add_action( 'init', 'my_plugin_register_icon_collection' );

The first argument is the collection name. It must start and end with a lowercase letter or digit, and in between may contain lowercase letters, digits, hyphens, and underscores. The second is an array with a required label and an optional description.

Removing a collection

Use wp_unregister_icon_collection() with the collection name. Removing a collection also removes every icon in it, so you don’t need to remove the icons one by one. Run this on the init hook, at a later priority than the registration so the collection already exists:

function my_plugin_unregister_icon_collection() {
	wp_unregister_icon_collection( 'my-plugin' );
}
add_action( 'init', 'my_plugin_unregister_icon_collection', 20 );

Icons

Every icon name has the form collection/icon-name, for example my-plugin/star. The collection must already be registered when you register the icon.

The icon-name part follows the same rule as a collection name: it must start and end with a lowercase letter or digit, and in between may contain lowercase letters, digits, hyphens, and underscores.

Adding icons

Register an icon with wp_register_icon(). You give it a label and the SVG itself — either inline as a string (content) or as an absolute path to an .svg file (file_path). Use one or the other, not both.

Like the other registration functions, wp_register_icon() returns true on success and false on failure, emitting a _doing_it_wrong() notice that explains why. Registration fails for an invalid name, a name that isn’t namespaced as collection/icon-name, a collection that isn’t registered, a duplicate icon, a missing label, unsupported argument keys, or providing neither content nor file_path (or both).

The SVG is sanitized through wp_kses against a small allowlist: only the <svg>, <path>, and <polygon> elements survive, each limited to a fixed set of attributes. Anything outside that subset — other elements, inline styles, scripts, or event handlers — is stripped. This allowlist is intentionally conservative and may be broadened in the future to cover more shape elements and attributes; see https://github.com/WordPress/gutenberg/pull/75550 for the ongoing work.

Note that file_path is read lazily: the file isn’t opened at registration, only when the icon’s content is first needed, during REST retrieval or rendering. So registration can succeed even if the path is wrong; a missing or unreadable file surfaces later as empty content, not as a registration error. Make sure the path resolves on the environment where the icon is used.

Icons can go into any registered collection. Most of the time, registering them under your own collection keeps them clearly separated from core’s.

function my_plugin_register_icons() {
	// Register a custom collection first, then add icons to it.
	wp_register_icon_collection(
		'my-plugin',
		array(
			'label' => __( 'My Plugin Icons', 'my-plugin' ),
		)
	);

	// An icon from an inline SVG string.
	wp_register_icon(
		'my-plugin/star',
		array(
			'label'   => __( 'Star', 'my-plugin' ),
			'content' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2l2.9 6.9 7.1.6-5.4 4.7 1.6 7L12 18l-6.2 3.2 1.6-7L2 9.5l7.1-.6z" /></svg>',
		)
	);

	// An icon from an .svg file shipped with the plugin.
	wp_register_icon(
		'my-plugin/heart',
		array(
			'label'     => __( 'Heart', 'my-plugin' ),
			'file_path' => plugin_dir_path( __FILE__ ) . 'icons/heart.svg',
		)
	);
}
add_action( 'init', 'my_plugin_register_icons' );

Removing an icon

Remove a single icon by name with wp_unregister_icon(), again on the init hook after the icon has been registered. Like the registration functions, it returns true on success and false on failure, emitting a _doing_it_wrong() notice; the only failure case is that the icon isn’t registered.

function my_plugin_unregister_icon() {
	wp_unregister_icon( 'my-plugin/star' );
}
add_action( 'init', 'my_plugin_unregister_icon', 20 );

To remove every icon in a collection at once, remove the collection instead (see Removing a collection).

Icon block enhancement

The icon picker in the core Icon block now groups icons by collection, so custom icons from plugins and themes appear alongside the core ones.

Each collection has its own tab, plus an “All” tab covering every collection at once. Search filters the selected collection; use the All tab to search across collections. The search query is preserved when switching tabs.

Icon picker

The block itself picked up a few more changes:

  • Flip and rotate. The toolbar now has controls to flip the icon horizontally or vertically, and a button that rotates it 90 degrees at a time.
  • Default icon. A newly inserted Icon block now starts with core/info instead of an empty placeholder.
  • Server rendering via wp_get_icon(). The block’s server-side render now delegates to wp_get_icon() to produce the SVG markup, so a block-rendered icon and one you print yourself with wp_get_icon() go through the same code path.

Rendering an icon in PHP

Use wp_get_icon() to get the SVG markup for any registered icon, ready to print:

// A decorative icon at the default 24px size.
echo wp_get_icon( 'core/plus' );

// A 32px icon with an accessible label and an extra CSS class.
echo wp_get_icon(
	'my-plugin/star',
	array(
		'size'  => 32,
		'label' => __( 'Featured', 'my-plugin' ),
		'class' => 'my-plugin-star',
	)
);

The first argument is the icon name. If it isn’t registered, you get an empty string. The optional second argument accepts:

  • size — Width and height in pixels. Defaults to 24. Pass null to keep the SVG’s own size.
  • class — Extra CSS class names for the <svg> element.
  • label — An accessible label. If you provide one, the icon is announced to screen readers; if you leave it out, the icon is treated as decorative and hidden from them.

Styling icons

Styling React icons from @wordpress/icons

Since version 15.0.0, each icon declares fill="currentColor" on its outer <svg>, so a React-rendered icon follows the current text color out of the box. By default the icon inherits color from its ancestors. To apply a different color, set color rather than fill — pass style={ { color } } to the icon:

import { Icon, plus } from '@wordpress/icons';

<Icon icon={ plus } style={ { color: '#3858e9' } } />;

Styling the SVG returned by wp_get_icon()

This markup is sanitized on registration, and the allowlist keeps fill only on the <path> and <polygon> shapes — not on the outer <svg> — and doesn’t permit stroke anywhere, so a stroke-based icon loses its stroke and you should stick to fill-based shapes for now. (This is a current limitation: the allowlist may be relaxed in the future to cover stroke and other attributes; see https://github.com/WordPress/gutenberg/pull/75550 for the ongoing work.) One upshot is that the fill="currentColor" the React icons carry on their <svg> does not survive here, and wp_get_icon() doesn’t add one. That has two consequences:

  • Inside the Icon block, coloring still works, because the block’s stylesheet sets fill: currentColor on .wp-block-icon svg. A block-rendered icon therefore follows the text color.
  • A standalone wp_get_icon() call returns bare markup with no such rule, so by default it renders in the SVG’s own fill (black), not the surrounding text color.

To make a standalone icon follow the text color, you have two options.

Supply your own CSS. Render the icon with a class (wp_get_icon() puts it on the <svg>), then set fill on it — since fill is inherited, it cascades to the shapes:

echo wp_get_icon( 'my-plugin/star', array( 'class' => 'my-icon' ) );
.my-icon {
	fill: currentColor;
}

Alternatively, put fill="currentColor" on the shape when you register the icon. The allowlist keeps fill on <path> and <polygon>, so it survives sanitization and the icon carries its own color behavior wherever it’s rendered:

wp_register_icon(
	'my-plugin/star',
	array(
		'label'   => __( 'Star', 'my-plugin' ),
		'content' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2l2.9 6.9 7.1.6-5.4 4.7 1.6 7L12 18l-6.2 3.2 1.6-7L2 9.5l7.1-.6z" /></svg>',
	)
);

REST API

The editor reads icons and collections over the REST API, and your own code can too. These endpoints are read-only: every route is a GET, so you can browse registered icons and collections but can’t register or change them over REST. All endpoints are under wp/v2 and require an authenticated user who can edit_posts, or who holds the equivalent edit capability for any REST-visible (show_in_rest) post type.

Collections:

  • GET /wp/v2/icon-collections — All collections.
  • GET /wp/v2/icon-collections/<collection> — A single collection.

Each collection is returned as an object with slug, label, and description. For example, GET /wp/v2/icon-collections/core returns:

{
	"slug": "core",
	"label": "WordPress",
	"description": "Default icon collection."
}

Icons:

  • GET /wp/v2/icons — All icons. (introduced in 7.0 with the name, label, and content fields and the search parameter; 7.1 adds the collection field and parameter)
  • GET /wp/v2/icons/<collection> — Icons in one collection. (new in 7.1)
  • GET /wp/v2/icons/<collection>/<name> — A single icon. (introduced in 7.0)

Each icon is returned as an object with name (the full collection/icon-name), label, content (the sanitized SVG markup), and collection (the slug of the collection it belongs to). For example, GET /wp/v2/icons/core/plus returns:

{
	"name": "core/plus",
	"label": "Plus",
	"content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewbox=\"0 0 24 24\"><path d=\"M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z\" /></svg>",
	"collection": "core"
}

The icon list also accepts two query parameters: search to filter by name or label, and collection to limit results to one collection. For example:

GET /wp/v2/icons?collection=my-plugin&search=star

Props to @tyxla for review.

#7-1, #dev-notes, #dev-notes-7-1

Fetched July 24, 2026