Skip to content

Conversation

@t-hamano
Copy link
Contributor

@t-hamano t-hamano commented Oct 9, 2025

What?

This PR is an attempt to implement an official API for registering and exposing SVG icons in WordPress.

Part of #16484
Blocker for #71227

Implementation details

This API basically consists of the following two classes:

WP_Icons_Registry

This class is very similar to WP_Block_Patterns_Registry. For now, I registered all SVG icons that exist in @wordpress/icons package.

The initial implementation will not allow external icon registration, but in the future code like this will work:

$registry = WP_Icons_Registry::get_instance();
$registry->register(
	'myplugin/myicon',
	array(
		'name'    => 'myplugin/myicon',
		'content' => '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/></svg>',
	)
);

// or

register_icon(...)

Registered icons can be rendered using code like this:

$registry = WP_Icons_Registry::get_instance();
$icon     = $registry->get_registered( 'core/chevron-left' );
echo $icon['content'];

WP_REST_Icon_Controller

Expose a list of icons registered via the icon registry.

Testing Instructions

REST API

  • Open a post.
  • Run the following code via your browser console.

Get all icons

await wp.apiFetch({ path: '/wp/v2/icons' });

Search icons

await wp.apiFetch({ path: '/wp/v2/icons?search=arrow' });

Get only specific fields

await wp.apiFetch({ path: '/wp/v2/icons?_fields=name' });

Get a specific icon

await wp.apiFetch({ path: '/wp/v2/icons/core/chevron-left' });

Frontend Rendering

The following code is an example that filters the Paragraph block and outputs one of the registered icons.

function gutenberg_render_paragraph_icon() {
	$registry = WP_Icons_Registry::get_instance();
	$icon     = $registry->get_registered( 'core/chevron-left' );
	return $icon['content'];
}
add_filter( 'render_block_core/paragraph', 'gutenberg_render_paragraph_icon', 10, 2 );

Future plans

This is just my personal guess, any feedback is welcome.

Icons as a post type

  • Create a built-in post type with the slug wp_icon. The SVG data is saved in the post content.
  • Registered icons will be exposed to both the registry and the REST API along with the core icons.
  • Uploading icon via the Icon block means the creation of a new post.
  • Taxonomies, or icon categories, can be registered. This may be implemented by the WP_Icon_Categories_Registry class.

Icons Library

  • A view for listing, searching, registering, duplicating, and deleting icons in the site editor.

Customizable icons for core blocks

For example, you can change the navigation toggle icon however you like using the Icon API.

Inline format

Format for injecting SVG elements into RichText

ToDo

  • Add a core backport PR and add a backport-changelog

@t-hamano t-hamano added the [Type] New API New API to be used by plugin developers or package users. label Oct 9, 2025
Comment on lines 20 to 25
$icons_directory = __DIR__ . '/../packages/icons/src/library/';
if ( ! is_dir( $icons_directory ) ) {
return;
}

$svg_files = glob( $icons_directory . '*.svg' );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In WordPress core, the problem is how to handle this. For example, it might be necessary to synchronize the SVG icon of packages/icons/src/library with wp-includes/icons/.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First step would be #72299, after which we can open a Trac ticket extending the copy logic in tools/webpack/packages.js.

* }
* @return bool True if the icon was registered with success and false otherwise.
*/
private function register( $icon_name, $icon_properties ) {
Copy link
Contributor Author

@t-hamano t-hamano Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, we do not allow external icon registration, which means that even if a developer runs the following code, an error will occur:

WP_Icons_Registry_Gutenberg::get_instance()->register();

* @return string The sanitized icon SVG content.
*/
private function sanitize_icon_content( $icon_content ) {
$allowed_tags = array(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 42 to 43
'name' => $icon_name,
'content' => $svg_content,
Copy link
Contributor Author

@t-hamano t-hamano Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For actual use in the Icon Block, we may also need the label field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One outstanding question for me is whether we'd like to properly annotate SVGs with metadata like title/label and, maybe, keywords (e.g. to match "edit" to the pencil icon). That would be better than programmatically adjusting the case and spacing.

And, independently of the above, all the labels (and possibly keywords) would need to exist statically in the code in order for i18n to be possible. In its simplest form, a build step in wpdev might, for instance, generate a PHP file with these strings. There is also JSON-based i18n (developed for block.json), in case we'd like to settle on defining metadata via JSON, although I'm less familiar with how that works.

Comment on lines 74 to 76
if ( current_user_can( 'edit_posts' ) ) {
return true;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to expose icons externally, but for now this is restricted. External icons are rendered by the WP_Icons_Registry class.

@t-hamano t-hamano mentioned this pull request Oct 9, 2025
2 tasks
lib/rest-api.php Outdated
Comment on lines 43 to 47
function gutenberg_register_icon_controller_endpoints() {
$icons_registry = new WP_REST_Icon_Controller_Gutenberg();
$icons_registry->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_icon_controller_endpoints' );
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to override the endpoints of the WP_REST_Icon_Controller class that will be implemented in core, allowing us to continue developing the endpoints on the Gutenberg plugin.

@github-actions
Copy link

github-actions bot commented Oct 9, 2025

Flaky tests detected in 62d53f4.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/18378781319
📝 Reported issues:

@t-hamano
Copy link
Contributor Author

t-hamano commented Oct 9, 2025

Ok, except for preparing the core backports PR, I think this PR is ready for review.

@t-hamano t-hamano changed the title [WIP] add icons API Add icons API Oct 9, 2025
@t-hamano t-hamano marked this pull request as ready for review October 9, 2025 14:56
@t-hamano t-hamano requested a review from spacedmonkey as a code owner October 9, 2025 14:57
@t-hamano t-hamano self-assigned this Oct 9, 2025
@t-hamano t-hamano changed the title Add icons API SVG Icon registration API Oct 9, 2025
@github-actions
Copy link

github-actions bot commented Oct 9, 2025

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: t-hamano <[email protected]>
Co-authored-by: mcsf <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Copy link
Contributor

@mcsf mcsf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@t-hamano: this looks pretty good for a first PR! It's been a few months since we last discussed this. Do you feel it's missing anything?

'gutenberg-experiments',
'gutenberg_experiments_section',
array(
'label' => __( 'Enables the SVG Icon registarion API.', 'gutenberg' ),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'label' => __( 'Enables the SVG Icon registarion API.', 'gutenberg' ),
'label' => __( 'Enables the SVG Icon registration API.', 'gutenberg' ),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion aside, I'm not totally sure we need the experiment. I suppose it depends on the initial scope of the registry. The more locked down it is, the less the need for an experiment gate, and vice versa.

Edit: OK, I think it's fair to start conservatively with the experiment, as long as we don't forget to remove it as soon as possible. :)

Comment on lines +21 to +24
$icons_directory = __DIR__ . '/../../packages/icons/src/library/';
if ( ! is_dir( $icons_directory ) ) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preliminary thoughts on making this work reliably across Gutenberg and Core:

  • Write a build step in wordpress-develop to copy icons from the Icons package to somewhere in wp-includes (I have a Git stash back from October that I need to resume and share).
  • In the Core backport, replace the hardcoded path (packages/icons/src/library) with a hardcoded path under wp-includes. The problem here is that users who will be simultaneously using 7.0 and Gutenberg won't see new icons added by the plugin.
    • This could be solved using a filter (e.g. wp_icons_registry_directory), but the problem there is that the filter is unavoidably "public" and can be abused.
    • Also using a filter, but rescinding even more control, we could do as in parse_blocks: $registry_class = apply_filters( 'icons_registry_class', 'WP_Icons_Registry' ); new $registry_class();
    • This sounds awful, but I'll share for the sake of completeness: we could hardcode a Gutenberg dependency in the Core class, e.g. if Gutenberg is active then path := wp-content/gutenberg/... else path := wp-include/....
    • We could do some creative "hook juggling" so that Gutenberg can define the class before Core, but I always worry about the fragility of these setups (and, if I remember correctly, we had some issues in the early 5.0 days).
    • Am I missing a better solution? If not, at the moment I believe the wp_icons_registry_directory filter is the best one.

Comment on lines +112 to +136
$allowed_tags = array(
'svg' => array(
'class' => true,
'xmlns' => true,
'width' => true,
'height' => true,
'viewbox' => true,
'aria-hidden' => true,
'role' => true,
'focusable' => true,
),
'path' => array(
'fill' => true,
'fill-rule' => true,
'd' => true,
'transform' => true,
),
'polygon' => array(
'fill' => true,
'fill-rule' => true,
'points' => true,
'transform' => true,
'focusable' => true,
),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you arrive at this ruleset? By inspecting current icons? How confident can we be about it?

Edit: ah, I see from #72215 (comment) that it's from Twenty Twenty. We should add a comment to make that explicit.

Comment on lines +206 to +219
'properties' => array(
'name' => array(
'description' => __( 'The icon name.', 'gutenberg' ),
'type' => 'string',
'readonly' => true,
'context' => array( 'view', 'edit', 'embed' ),
),
'content' => array(
'description' => __( 'The icon content (SVG markup).', 'gutenberg' ),
'type' => 'string',
'readonly' => true,
'context' => array( 'view', 'edit', 'embed' ),
),
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine to start with, but let's make a mental note about properties to add soon:

  • translatable label
  • URL to SVG file
  • internal URI to SVG file
  • keywords
  • (maybe) categories

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] New API New API to be used by plugin developers or package users.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants