Kian Babaabady

Safesprite Icons: why I wrote another SVG icon plugin

Almost every WordPress project ends up needing custom icons: brand icons, menu icons, icons on cards. The usual options are to enable SVG uploads in the Media Library with a plugin, or to paste raw SVG markup into the theme or page builder.

The first opens a door WordPress keeps closed on purpose. An SVG file is XML and can carry scripts, styles or references to remote resources. The second repeats the same SVG markup everywhere the icon appears; one icon used in a menu and a loop of cards can be duplicated dozens of times in the page HTML.

I wrote Safesprite Icons for that problem. Version 1.0.0 went up on the WordPress.org plugin directory in September 2026, and the code is on GitHub. It's still new and has very few users; this post is mostly about why and how it's built.

What it does#

  • A separate icon library in wp-admin, organised in collections (folders). SVG uploads are only accepted there.
  • It does not enable SVG uploads in the Media Library, so it isn't a Safe SVG / SVG Support style plugin.
  • One shared icon picker for the Gutenberg block, the Elementor widget, the Classic Editor (TinyMCE) and nav menu items.
  • For developers: the [sfsi_icon slug="my-icon"] shortcode and the sfsi_icon() PHP helper.
  • Icons and collections can be renamed without changing their slugs, so existing shortcodes and blocks keep working. Deletes go to a trash first.

How it works#

1. Sanitising on upload#

Each file is first checked against a size limit you can change in the settings. Then it's sanitised with the enshrined/svg-sanitize library, which ships inside the plugin.

After that I added another pass: <style> elements, style attributes and any href that isn't an internal fragment (#...) are removed. A final check rejects the file outright if anything like a style or an http:, data: or javascript: link is still there. I'd rather reject a suspicious file than let something slip through.

Clean files are stored in randomly named subfolders under wp-content/uploads/safesprite-icons/. Metadata lives in the plugin's own tables, not in a custom post type.

2. Light output with <use>#

When an icon is used (block, shortcode or PHP), the output is just a small SVG that references a symbol:

<?php echo sfsi_icon( 'my-icon', array( 'size' => 24 ) ); ?>

which prints roughly:

<svg class="sfsi-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false" style="width:24px;height:24px">
  <use href="#sfsi-icon-my-icon" xlink:href="#sfsi-icon-my-icon"></use>
</svg>

Numeric sizes are clamped to 16–256 px, and the colour is either inherited from the surrounding text or a sanitised custom value. At the same time the icon's slug is recorded in a per-request registry.

On wp_footer, the plugin takes only the icons registered on that page and prints one <symbol> per icon inside a hidden SVG. Each icon appears once in the page no matter how many times it's used.

Before printing, the file's SHA-256 hash is compared with the hash stored at upload time. If the file on disk has changed, that icon is skipped.

Things that came up along the way#

Most of the work started when I wanted the picker to behave properly in the editors. For example, the Elementor editor canvas doesn't have the footer spritesheet, so it needs an inline preview there. I also had a bug where saving a menu wiped the icons from its items. These are all in the changelog.

Limitations#

  • The current version only supports icons you upload to your own site; there are no icon CDNs or bundled icon sets.
  • It needs PHP 7.4+ with the dom and libxml extensions, and WordPress 6.0+.
  • Data is kept on uninstall by default. Turn on the option in the settings if you want tables and files removed too.

If you try it and hit a problem or have an idea, open an issue on GitHub, post in the WordPress.org support forum, or just email me.

← All writing