How to Add Custom Social Icons in WordPress Correctly

I had a client—a sharp little design agency—who was getting endlessly frustrated. They built their site with a block theme, keeping it super clean. But they needed to add social icons for their clients’ sites, for services like Ko-fi, Letterboxd, and other niche platforms. The default Social Icons block didn’t have them, and their only option was to install some bloated “Ultimate Social Widgets” plugin just for a couple of missing icons. What a mess.

For years, the solution has been hacky. You either jam in a custom HTML block, which clients will absolutely destroy, or you create a custom block from scratch. Total overkill. But with the changes coming in WordPress 6.9, we can now register custom social icons the right way. And trust me on this, it’s a big deal for keeping sites lean.

A Real API for Custom Social Icons

The problem is two-fold. First, you have to tell the block editor about your new icon so you can select it. Second, you have to tell WordPress how to actually display that icon on the front end of the site. If you miss that second step, you get a blank space. It’s a common mistake.

My first thought was to just handle it with CSS. Register the variation in JavaScript, then use a CSS `:before` pseudo-element with an SVG background to slap the icon on the front end. And yeah, it might look right, but it’s a hack. It’s terrible for accessibility and a nightmare to maintain. The real fix has to be at the PHP level, telling WordPress what the icon actually is.

The process involves two key steps. First, we use JavaScript to register a variation for the core/social-link block. This makes our icon available in the editor. The official WordPress Developer Blog recently posted a fantastic deep-dive into this, which you can read at https://developer.wordpress.org/news/2025/08/registering-custom-social-icons-in-wordpress-6-9/.

import { registerBlockVariation } from '@wordpress/blocks';
import {SVG, Path} from '@wordpress/primitives';

registerBlockVariation('core/social-link', {
	name: 'kofi',
	title: 'Ko-fi',
	icon: (
		<SVG role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
			<Path d="M11.351 2.715c-2.7 0-4.986.025-6.83.26C2.078 3.285 0 5.154 0 8.61c0 3.506.182 6.13 1.585 8.493 1.584 2.701 4.233 4.182 7.662 4.182h.83c4.209 0 6.494-2.234 7.637-4a9.5 9.5 0 0 0 1.091-2.338C21.792 14.688 24 12.22 24 9.208v-.415c0-3.247-2.13-5.507-5.792-5.87-1.558-.156-2.65-.208-6.857-.208m0 1.947c4.208 0 5.09.052 6.571.182 2.624.311 4.13 1.584 4.13 4v.39c0 2.156-1.792 3.844-3.87 3.844h-.935l-.156.649c-.208 1.013-.597 1.818-1.039 2.546-.909 1.428-2.545 3.064-5.922 3.064h-.805c-2.571 0-4.831-.883-6.078-3.195-1.09-2-1.298-4.155-1.298-7.506 0-2.181.857-3.402 3.012-3.714 1.533-.233 3.559-.26 6.39-.26m6.547 2.287c-.416 0-.65.234-.65.546v2.935c0 .311.234.545.65.545 1.324 0 2.051-.754 2.051-2s-.727-2.026-2.052-2.026m-10.39.182c-1.818 0-3.013 1.48-3.013 3.142 0 1.533.858 2.857 1.949 3.897.727.701 1.87 1.429 2.649 1.896a1.47 1.47 0 0 0 1.507 0c.78-.467 1.922-1.195 2.623-1.896 1.117-1.039 1.974-2.364 1.974-3.897 0-1.662-1.247-3.142-3.039-3.142-1.065 0-1.792.545-2.338 1.298-.493-.753-1.246-1.298-2.312-1.298"/>
		</SVG>
	),
	attributes: {
		service: 'kofi',
	},
	isActive: ['service']
});

Next, we use the new block_core_social_link_get_services filter in PHP to make the SVG available on the server so it renders on the front end. Here’s the kicker: this simple filter is what we’ve been missing for years.

add_filter( 'block_core_social_link_get_services', 'devblog_social_link_services' );

function devblog_social_link_services(array $services_data) {
	$services_data['kofi'] = [
		'name' => 'Kofi',
		'icon' => '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M11.351 2.715c-2.7 0-4.986.025-6.83.26C2.078 3.285 0 5.154 0 8.61c0 3.506.182 6.13 1.585 8.493 1.584 2.701 4.233 4.182 7.662 4.182h.83c4.209 0 6.494-2.234 7.637-4a9.5 9.5 0 0 0 1.091-2.338C21.792 14.688 24 12.22 24 9.208v-.415c0-3.247-2.13-5.507-5.792-5.87-1.558-.156-2.65-.208-6.857-.208m0 1.947c4.208 0 5.09.052 6.571.182 2.624.311 4.13 1.584 4.13 4v.39c0 2.156-1.792 3.844-3.87 3.844h-.935l-.156.649c-.208 1.013-.597 1.818-1.039 2.546-.909 1.428-2.545 3.064-5.922 3.064h-.805c-2.571 0-4.831-.883-6.078-3.195-1.09-2-1.298-4.155-1.298-7.506 0-2.181.857-3.402 3.012-3.714 1.533-.233 3.559-.26 6.39-.26m6.547 2.287c-.416 0-.65.234-.65.546v2.935c0 .311.234.545.65.545 1.324 0 2.051-.754 2.051-2s-.727-2.026-2.052-2.026m-10.39.182c-1.818 0-3.013 1.48-3.013 3.142 0 1.533.858 2.857 1.949 3.897.727.701 1.87 1.429 2.649 1.896a1.47 1.47 0 0 0 1.507 0c.78-.467 1.922-1.195 2.623-1.896 1.117-1.039 1.974-2.364 1.974-3.897 0-1.662-1.247-3.142-3.039-3.142-1.065 0-1.792.545-2.338 1.298-.493-.753-1.246-1.298-2.312-1.298"/></svg>'
	];

	return $services_data;
}

So, What’s the Point?

The point is that we can finally extend a core block cleanly without replacing it or adding a bunch of dependencies. This is how it should be. It allows us to build more robust, lightweight sites for clients without telling them, “Sorry, WordPress doesn’t do that.” We can just make it happen. It’s a small change that signals a much bigger, healthier direction for core development.

Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.

Leave a Reply

Your email address will not be published. Required fields are marked *