How to add custom social icons in WordPress 6.9

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

For years the workaround has been hacky. You either jam in a custom HTML block, which clients will absolutely destroy, or you build a custom block from scratch, which is total overkill. With the changes coming in WordPress 6.9, we can finally register custom social icons the right way, and that is a big deal for keeping sites lean.

A real API for custom social icons

The problem is twofold. 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. Miss that second step and you get a blank space, which is a common mistake.

My first instinct was to handle it with CSS: register the variation in JavaScript, then use a CSS :before pseudo-element with an SVG background to drop the icon onto the front end. It might look right, but it’s a hack. It’s bad for accessibility and a pain to maintain. The real fix has to happen at the PHP level, telling WordPress what the icon actually is.

There are two steps. First, we use JavaScript to register a variation for the core/social-link block, which makes our icon available in the editor. The WordPress Developer Blog recently posted a detailed walkthrough of 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. That 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 piling on dependencies. It lets us build sturdier, lighter sites for clients instead of telling them, “Sorry, WordPress doesn’t do that.” We can just make it work. It’s a small change, but it points core development in a healthier direction.

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.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.