Tag: WordPress

  • Building a CSS Carousel WordPress Block

    Building a CSS Carousel WordPress Block

    Today I want to introduce you to the latest addition to CSS, specifically the CSS Overflow 5 specification. I don’t usually write about CSS features, but this one is particularly intriguing because it allows for pure CSS-based carousels — no JavaScript required! Why is this significant?

    The last few years have seen great improvements in the web performance space. We have been democratizing performance for WordPress and beyond. This trend continues also in 2025. New web platform features make it easier than ever to build websites with great performance, accessibility, and developer experience.

    For example, invoker commands enable building interactive UIs declaratively using only HTML attributes. Similarly, there are new enhancements in the works to easily build tooltips. They use a combination of popover="hint", CSS anchor positioning, and interest invokers. Full customization of <select> elements is possible too. And now, CSS carousels.

    According to HTTP Archive, over 54% of all WordPress sites use a JavaScript slider library (source). These libraries are often quite heavy, negatively impacting performance. For example, Swiper is a whopping 140KB (minified). With CSS carousels, there is no need for such libraries anymore!

    Why CSS carousels are superior

    Here’s why I am excited about this new web platform addition:

    1. Great performance
      Less JavaScript means better performance for your website. This CSS-powered solution also won’t block the main thread like some of these JavaScript libraries. That means it will always perform better than any JavaScript solution.
    2. Strong accessibility
      Carousel best practices such as keyboard support are handled by the browser, so you don’t have to worry about it. Sure, you might be able to build an equally accessible carousel yourself. However, it would cost you a lot more time (and code). Plus, CSS carousels work even with JavaScript disabled.
    3. Excellent developer experience
      No external dependencies, strong accessibility, and performance out of the box. CSS carousels make maintenance a bliss, saving you lots of valuable time now and in the future.
    CSS carousel demo from the Chrome for Developers blog

    Right now this feature is available in Chrome (as of version 135). Other browsers will probably follow soon. Meanwhile, they could fall back to existing JavaScript solutions.

    Key ingredients for a CSS-only carousel

    At the core of a basic carousel is a scrolling list of items (either horizontal or vertical). Ideally combined with some scroll snapping (where content “snaps” into place as you scroll) to ensure that the items are always nicely visible. It could look something like this:

    <ul class="carousel">
      <li><img ...></li>
      <li><img ...></li>
      <li><img ...></li>
    </ul>Code language: HTML, XML (xml)
    .carousel {
      display: grid;
      grid-auto-flow: column;
      grid-auto-columns: 100%;
      overflow-x: auto;
      overscroll-behavior-x: contain;
      scroll-snap-type: x mandatory;
      anchor-name: --carousel;
    
      > li {
        scroll-snap-align: center;
      }
    }Code language: CSS (css)

    Carousels often have arrows on the side to go forward and backward (or up and down). With CSS Overflow Level 5, these arrows can now be easily added using the new ::scroll-button() pseudo-elements. For example:

    .carousel {
      /* for the left/right arrows */
      /* Note: they are actually siblings of .carousel */
      &::scroll-button(*) {
        position: fixed;
        position-anchor: --carousel;
      }
    
      &::scroll-button(inline-start) {
        position-area: inline-start center;
        /* emoji/icon with alt text for accessibility */
        content: "⬅️" / attr(data-previous);
    }
    
      &::scroll-button(inline-end) {
        position-area: inline-end center;
        content: "➡️" / attr(data-next);
      }
    }Code language: CSS (css)

    Similarly, if you want to add “dots” below the carousel, you can do so using the new ::scroll-marker (the dot) and  ::scroll-marker-group (wraps all the dots) pseudo-elements. It could look like this:

    .carousel {
      scroll-marker-group: after;
    
      /* Note: this is actually a sibling of .carousel */
      &::scroll-marker-group {
        display: grid;
        grid-auto-columns: 20px;
        grid-auto-flow: column;
        gap: 20px;
      }
    
      /* This will add one dot for each <li> */
      & > li::scroll-marker {
        content: "⭕";
        cursor: pointer;
        aspect-ratio: 1;
    
        &:target-current {
          content: "🔴";
        }
      }
    }Code language: CSS (css)

    Finally and optionally, you could use CSS inertness to hide any “hidden” carousel elements from the accessibility tree. This of course depends on your use case. The behavior is the same as when using the HTML inert attribute, but defined using CSS. Example:

    .carousel {
      > li {
        container-type: scroll-state;
    
        > .card {
          @container not scroll-state(snapped: x) {
            interactivity: inert;
            opacity: .25;
          }
        }
      }
    }Code language: CSS (css)

    These are all the basic components you need to create a carousel using modern CSS. The result is pretty impressive:

    If you are using Chrome 135 or later, you should see a carousel complete with arrows, dots, and keyboard support. In other browsers you simply get a swipeable set of images. Here’s a screenshot:

    A simple CSS carousel with arrow navigation and dots below the carousel
    The combined CSS in action to build a fully functioning carousel. Note the filled dot and the disabled arrow on the last slide.

    But CSS carousels can do a lot more. If you would like some inspiration, I highly recommend checking out the Carousel Gallery site with tons of incredible demos. Each demo covers a real use case found on the internet. They all show how to orchestrate scroll buttons and markers with scroll-driven animations, scroll-state() queries, and much more. Here’s one of the demos of a e-commerce product image gallery using a CSS carousel:

    You can build a gallery of product images for your online store using only CSS

    Want to get more hands on and dive right in? Try the Carousel Configurator to help visualize the capabilities of CSS-only carousels.

    Building a CSS carousel WordPress block

    As I mentioned earlier, over 54% of all WordPress sites use at least one of the popular slider libraries. So it’s no wonder that there have been discussions about introducing a first-party carousel block for many years. One of the main blockers was of course the reliance on JavaScript.

    In 2025, building a performant, accessible, and maintainable carousel using only CSS finally becomes a breeze. To prove it, let’s apply these new features in WordPress by building a custom carousel block!

    Tip: If you want to see the final result, check out this GitHub repository.

    Setting up a carousel block variation

    For the sake of simplicity, I am not going to build a completely new block from the ground up. Instead, I am leveraging the Block Variations API to make a carousel a variation of the gallery block. This saves a lot of boilerplate code.

    The nice thing about block variations is that I can set them up entirely using PHP. I only need some JavaScript to update the block’s HTML class attribute for properly targeting the carousel variation using CSS. Here’s an excerpt:

    /**
     * Adds carousel variation to gallery block.
     *
     * @param array    $variations List of all block variations.
     * @param WP_Block $block_type Block type instance.
     * @return array Filtered list of block variations.
     */
    function css_carousel_block_add_carousel_variation( $variations, $block_type ) {
    	if ( 'core/gallery' !== $block_type->name ) {
    		return $variations;
    	}
    
    	$variations[] = array(
    		'name'        => 'carousel',
    		'title'       => __( 'Carousel', 'css-carousel-block' ),
    		'description' => __( 'A carousel', 'css-carousel-block' ),
    		'scope'       => array( 'block', 'inserter', 'transforms' ),
    		'isDefault'   => false,
    		'attributes'  => array(
    			'displayType' => 'carousel',
    		),
    	);
    
    	return $variations;
    }
    
    add_filter( 'get_block_type_variations', 'css_carousel_block_add_carousel_variation', 10, 2 );Code language: PHP (php)

    Check out the GitHub repository for the full code.

    CSS carousels in the editor

    The block markup on the frontend and in the editor is very similar. That means we can use the same CSS in both places, making for a true WYSIWYG experience. Additional bonus: we do not have to use any janky JavaScript library in the block editor that messes with React. The result:

    CSS carousel block variation in the editor
    CSS carousel block variation in the editor.

    While the CSS carousel already works great in the editor, there are some minor differences to the frontend.

    For example, the translated, human-readable labels for the scroll markers (arrows) are added via data-* attributes on the frontend. This way, they can be used in the content CSS property. Unfortunately, I couldn’t find a suitable filter in the editor to achieve the same.

    Second, in the editor we need to cater for the gallery block’s inner image blocks and their block toolbar. It looks odd when the toolbar stays in place when swiping through the carousel. This would be a great opportunity to use new scroll snap events such as scrollSnapChange. We can hide the toolbar when swiping and automatically select the next block upon slide change.

    Further iterating on this CSS carousel block

    Turning a basic gallery block into a feature-rich carousel is surprisingly easy. But of course this is a mere proof of concept. There is a lot more that could be done.

    For instance, the scroll buttons and markers in this demo are positioned using the new CSS anchor positioning API. This requires each carousel to have its unique anchor name. That’s something I haven’t implemented yet in this proof of concept.

    In addition to that, cross-browser support is an important consideration when implementing such new features. Since CSS carousels are currently only supported by Chrome, there could be a fallback solution using JavaScript in other browsers. This is very easy to implement. Check out this pull request for the Twenty Fourteen theme for an example.

    It’s also worth mentioning that work on CSS carousels is still ongoing. For example, work is already being done to let you add your own components for scroll buttons and markers. This makes it easier to add custom SVG icons or use Tailwind classes. Another highly requested feature is support for cyclic scrolling. Right now this still requires JavaScript.

    Share your feedback

    Long story short, CSS carousels are really powerful. WordPress users and contributors have been wanting a carousel block for years. This proof of concept could be the start of it. And if you are a developer who builds carousels for clients’ websites, why use this new feature in your next project? That’s why I would love for you to try it out, provide feedback, and help make it happen.

  • Enabling AI-Powered WordPress Development with WP-CLI and the Model Context Protocol (MCP)

    Enabling AI-Powered WordPress Development with WP-CLI and the Model Context Protocol (MCP)

    Last week I participated at the CloudFest Hackathon, where I was leading a project to bring the Model Context Protocol (MCP) to WordPress. I was intrigued by the challenge to try to bring this “USB-C port for AI applications” to the space I know best.

    The goal of this project was to enable AI-powered WordPress development by implementing MCP in the WordPress ecosystem and exposing it through WP-CLI, which is the official command line interface for WordPress. So for once I was working on server-side AI and not Web AI.

    WordPress development workflows currently lack seamless integration with AI capabilities, particularly during local development. While REST API endpoints enable AI interactions with live sites, developers working with local WordPress installations have limited options for AI-assisted content creation and site management.

    This project transforms WordPress into an MCP Server and WP-CLI into an MCP Host through a new package, enabling direct AI interactions with WordPress installations during development. This approach provides developers with powerful AI capabilities without requiring a live site or REST API endpoints.

    I also tried explaining it in simpler terms in my pre-event interview with Christian Taylor:

    MCP in action

    Despite MCP being bleeding edge technology, the team managed to implement a very powerful server implementation that exposes any REST API route and WP-CLI command as function calling capabilities. A separate MCP client then is able to connect to this and other servers. I say bleeding edge because MCP is brand new and lacks thorough documentation and tooling in anything that’s not Python or JavaScript. The PHP MCP SDK we used is just an AI-generated clone of the Python version and could definitely be improved. Also, the protocol itself is still undergoing big changes, as indicated by this highly relevant RFC, which was published right after the Hackathon concluded.

    In addition to the MCP part, we implemented and further refined an LLM-agnostic CLI command for natural language processing. The project team used the AI Services plugin for this, but LLPhant would be a similar alternative. And ideally this would support local models too.

    In short, we set out to enable use cases such as $ wp ai "Summarize my latest GitHub issues in a new blog post", where the GitHub tool could be offered by one MCP server, and the blog post tool by another.

    The team even implemented image editing capabilities using the brand new Gemini 2.0 Flash native image generation that was just released days before the event. Here you can see it in action:

    Hat tip to Marco Chiesi for creating this nice demo minutes before the Hackathon deadline

    Hackathon experience

    It was my fourth time at the CloudFest Hackathon and the second time leading a project, but the first time doing it myself. Before the event I set up a basic GitHub repository as a starting point, but didn’t have much time to do prepare more (e.g. by creating some issues or diagrams). Then, after the project patch at the start of the hackathon, I was overwhelmed by the interest in this project. We ended up being 10 people from diverse backgrounds, which was really nice!

    The project team after the hackathon. Missing: Joost. Photo by Roan de Vries.

    The hackathon team:

    Thanks to these wonderful people we won the social media master award thanks to our outreach during the event (special thanks to Milana for a ton of great tweets!), and overall won second place!

    The project team on stage after winning the social media master award. Photo by Roan de Vries.

    Conclusion

    I am really excited about the future potential of the project and keen to continue working on it in some form or another. Especially the thought of implementing the MCP server in WordPress core or in a plugin is very promising.

    People interested in the project are encouraged to check out the GitHub repository to collaborate.

  • Invoker Commands in WordPress

    Invoker Commands in WordPress

    Recently, I looked more closely into the new Invoker Commands API and what it means for WordPress.

    There is a lot happening on the web platform right now, with tons of new features and capabilities landing in all browsers every month. For example, this January saw Promise.try and a new Device Posture API to detect devices with foldable screens. Interop 2024 also just concluded, with exciting new additions such as text-wrap: balance for balanced headlines and Popover for creating overlays declaratively using HTML. Interop 2025 is already coming soon and I am looking forward to seeing more features that help move the web forward.

    The Invoker Commands API is one of these new web platform APIs. It provides a way to declaratively assign behaviors to buttons to control interactive elements on the page. Think of a button that allows you to play or pause a video somewhere else, simply by adding some HTML attributes. Naturally, I set out see how I could use this intriguing new API in WordPress.

    The Invoker Commands API

    The Invoker Commands API is declarative, which means you use HTML attributes to use it, rather than writing imperative JavaScript code. It introduces two new HTML attributes:

    1. commandfor
      Add this to a <button> element and pass the ID of the interactive element to control.
    2. command
      Specifies the action to perform on the interactive element.

    Browsers already support some commands out of the box for native elements like <video>, <dialog> or Popover.

    Here are a couple of examples:

    Controlling a dialog

    <button commandfor="mydialog" command="show-modal">Show modal dialog</button>
    
    <dialog id="mydialog">
      <button commandfor="mydialog" command="close">Close</button>
      Dialog Content
    </dialog>Code language: HTML, XML (xml)

    Controlling a video/video

    <button type="button" commandfor="my-video" command="play-pause">Play/Pause</button>
    <button type="button" commandfor="my-video" command="toggle-muted">Mute/Unmute</button>
    
    <video id="my-video"></video>Code language: HTML, XML (xml)

    Custom commands

    The Invoker Commands API also enables you to add custom commands for your own interactive components on a page. They work essentially the same as built-in ones, with the exception that they must start with a double dash (--). Example:

    <button commandfor="my-img" command="--rotate-left">Rotate left</button>
    <button commandfor="my-img" command="--rotate-right">Rotate right</button>
    <img id="my-img" src="photo.jpg" alt="[add appropriate alt text here]" />Code language: HTML, XML (xml)

    But how do you tell the browser what --rotate-left and --rotate-right should do? Here’s where JavaScript comes in.

    When you click on the button with such a command, a JavaScript event is dispatched on the controlled element (the invokee), in this case the <img> element. You can then listen to this event and perform the desired action:

    myImg.addEventListener("command", (event) => {
      if (event.command == "--rotate-left") {
        myImg.style.rotate = "-90deg";
      } else if (event.command == "--rotate-right") {
        myImg.style.rotate = "90deg";
      }
    });Code language: JavaScript (javascript)

    Browser support

    A quick note on browser support: at the time of writing, this feature is available behind a flag in Chrome, Firefox, and Safari.

    A polyfill is also available, and it’s very small (~2KB minified + gzipped).

    Introducing Block Invokers

    With the basics covered, let’s switch to WordPress. When I first learned about this API and saw the examples, I automatically thought of a button block that controls a video block or a details block. It struck me as a really obvious use case, so I was keen to figure out how to make it happen. Additionally, I was curious to learn how this new feature relates to the Interactivity API in WordPress, which also uses a declarative way to achieve interactivity on a web page.

    I created an experimental Block Invokers WordPress plugin and all my findings can be found on GitHub. There you can also find a link to directly test it yourself using WordPress Playground.

    Providing invoker commands in a block

    I wanted to way to provide information about supported commands during block registration. Imagine this being part of the block.json metadata:

    {
        "$schema": "https://schemas.wp.org/trunk/block.json",
        "apiVersion": 3,
        "name": "my-plugin/awesome-video",
        "title": "Awesome Video",
        "category": "media",
        "parent": [ "core/group" ],
        // ...
        "commands": {
          "play-pause": {
                "label": "Play/Pause",
                "description": "If the video is not playing, plays the video. Otherwise pauses it."
          },
          "toggle-muted": {
                "label": "Toggle Muted",
                "description": "If the video is muted, it unmutes the video. Otherwise it mutes it."
          }
        ]
    }Code language: JSON / JSON with Comments (json)

    To achieve this, I used the block_type_metadata filter to declare the supported commands for all suitable blocks. For instance:

    • The core/video and core/audio blocks support play-pause, play, pause, and toggle-muted commands.
    • The core/details block supports the toggle, open, and close commands.
    • The core/image block supports custom --show-lightbox and --hide-lightbox commands.

    In the editor, I then added a new panel to the button block to configure commands. You can select from a list of all existing blocks on the page and their supported commands (if they have any). This way you can say “I want to perform command x on this particular block y.

    Block Invokers plugin UI in the WordPress block editor. It shows a first dropdown to select the block to control. The second dropdown allows you to select the invoker command to perform on the block.
    Block Invokers editor UI

    Now, the crucial part is to add a unique ID to both the chosen target block and also the button block, as it is required for the command attribute to establish the connection between the two elements. The editor stores the ID in a custom block attribute. On the frontend, the plugin adds it to the right elements using the HTML API.

    Because Invoker Commands is a declarative API using only HTML attributes, this is already enough to achieve basic interactivity like playing a video, without any JavaScript required.

    Reducing the amount of JavaScript needed on websites is a great example of democratizing performance.

    Supporting custom invoker commands

    However, I also wanted to support custom commands, and for those you need JavaScript event listeners. The image lightbox functionality was an ideal candidate to try this out with dedicated --show-lightbox and --hide-lightbox commands.

    Here’s where the Interactivity API comes into play. The image block has an extensive and complex store configuration with lots of state, actions, and callbacks. I was able to tap into that and add a new event listener using another HTML attribute: 'data-wp-on--command="actions.handleCommand". The action in the block’s view.js script is then very minimalistic as it can reuse existing logic to perform the right actions depending on the received command:

    handleCommand( event ) {
      switch ( event.command ) {
        case '--show-lightbox':
          actions.showLightbox();
          break;
        case '--hide-lightbox':
          actions.hideLightbox();
          break;
      }
    }Code language: JavaScript (javascript)

    This is like the myImg.addEventListener example earlier, just written in the Interactivity API way.

    Now, one could probably implement the previous examples with only the Interactivity API. However, that would require you to load additional JavaScript and write some JavaScript yourself for something that the browser supports out of the box.

    Also, it would require you to interact with another block’s data store, which isn’t really meant to be extended by other plugins or provide backward compatibility.

    The way I see it, the Invoker Commands API and the Interactivity API can actually work together very well. It allows a block to define a public API of supported interactions that other blocks or components can tap into. These can be either custom actions or default ones supported by the browser. Again, the latter would then work with zero additional JavaScript.

    Conclusion

    While the Invoker Commands API is still experimental, it already works very well and is very intuitive to use, even in a WordPress context. Blocks are the perfect place to make use of this new technology and I think this should be further explored. The Interactivity API is a great companion to Invoker Commands, so the two are not mutually exclusive.

    Browser support is very promising as well, and with the polyfill there is no reason to not look into this new feature already.

    My Block Invokers plugin is on GitHub if you want to check it out in more depth.

  • Web AI for WordPress

    The web platform team in Chrome is working on built-in AI features, where the browser provides AI models, including large language models (LLMs), to enable on-device AI for browser features and web platform APIs. This is a game changer and a huge opportunity for WordPress to democratize AI-assisted publishing. Let me tell you why.

    If WordPress does not want to fall behind its competitors, it must seamlessly provide typical AI features users nowadays expect from a publishing platform. There are already various AI WordPress plugins, but they all come at a cost—both literally and metaphorically. These plugins all rely on third-party server-side solutions, which impacts both your privacy and your wallet.

    Web AI has several key benefits over a server-side approach. It brings models to the browser, protecting sensitive data and improving latency.

    Web AI is the overarching term for the ability to run AI solutions in the browser using JavaScript, WebAssembly, and WebGPU. This space was pioneered by libraries such as TensorFlow.js and Transformers.js. Using those tools, websites can download models and run tasks of their choice directly in the browser.

    Chrome’s built-in API

    If everyone has to download and update these models all the time, this doesn’t really scale and isn’t really sustainable. That’s where Chrome’s built-in AI steps in. It is just one form of client-side AI or Web AI.

    With the built-in AI, your site or web app will be able to run various AI tasks against foundation and expert models without having to worry about deploying and managing said models. Chrome achieves this by making Gemini Nano available through dedicated web platform APIs, running locally on most modern computers.

    Note: This functionality is currently only available in Chrome Canary. Join the early preview program to learn how to access those early-stage built-in AI features and provide feedback.

    At the moment, the Chrome team expects the built-in AI to be beneficial for both content creation and content consumption. During creation, this could include use cases such as writing assistance, proofreading, or rephrasing. On the consumption side, typical examples are summarization, translation, categorization, or answering questions about some content.

    Early preview program participants will receive more detailed information about the following APIs:

    To give you an example, using the prompt API is pretty straightforward:

    const session = await ai.languageModel.create({
      systemPrompt: "You are a friendly, helpful assistant specialized in clothing choices."
    });
    
    const result = await session.prompt(`
      What should I wear today? It's sunny and I'm unsure between a t-shirt and a polo.
    `);
    
    console.log(result);
    
    const result2 = await session.prompt(`
      That sounds great, but oh no, it's actually going to rain! New advice??
    `);Code language: JavaScript (javascript)

    The other APIs are similarly straightforward to use. By the way, if you are an avid TypeScript user, there are already type definitions which I helped write.

    Eager to build something with this new API? The Google Chrome Built-in AI Challenge challenge invites developers to explore new ground by creating solutions that leverage Chrome’s built-in AI APIs and models, including Gemini Nano. Cash prizes totaling $65,000 will be awarded to winners.

    Web AI Advantages

    • The browser takes care of model distribution and updates for you, significantly reducing the complexity and overhead.
    • Everything is processed locally on-device, without sending your sensitive data elsewhere, keeping it safe and private.
    • No server round trips means you can get near-instant results.
    • You can access AI features even if you’re offline or have bad connectivity.
    • Save money by not having to use expensive third-party services or sending large models over a network.

    This is not the first time where doing things client-side is better than on the server.1 You could of course consider a hybrid approach where you handle most use cases on-device and then leverage a server-side implementation for the more complex use cases.

    Using built-in AI in WordPress

    If WordPress core wants to offer AI capabilities to each and everyone of its users, it can’t jeopardize user’s privacy by relying on expensive third-party services. The project also does not have the resources or infrastructure to maintain its own API and running AI inference on a shared hosting provider in PHP is also not really something that works.

    That’s why Web AI and particularly Chrome’s built-in AI are a perfect match for WordPress. With it, users benefit from a powerful Gemini Nano model that helps them accomplish everyday tasks.

    The modern and extensible architecture of the WordPress post editor (Gutenberg) makes it a breeze to leverage Chrome’s built-in AI. To demonstrate this, I actually built several AI experiments. They enhance the user experience and accessibility for both editors and readers.

    Here’s an overview:

    Provide tl;dr to visitors

    Screenshot of a "Summarize post content" button on a blog post, powered by Web AI.
    Web AI makes it easy to summarize content with the click of a button — all done on-device.

    Uses Chrome’s built-in summarization API to provide readers a short summary of the post content. The UI is powered by WordPress’ new Interactivity API.

    Writing meta descriptions based on the content

    Using the dedicated summarizer API, the content can be easily summarized in only a few sentences.

    “Help me write”

    Block toolbar in the WordPress editor with "Help me write" features, using Web AI to do things like rephrasing or shortening text.
    AI-powered “Help me write” quickly became an essential feature for many users.

    Options for rewriting individual paragraphs à la Google Doc, like rephrasing, shortening or elaborating.

    Generate image captions / alternative text

    Uses Transformers.js and Florence-2 to generate image captions and alternative text for images directly in the editor. Also integrated into Media Experiments, which supports video captioning too.

    Generate a memorable quote

    A slight variation on the summarization use case, this extracts a memorable quote from the article and gives it some visual emphasis.

    Assigning tags & categories to blog posts

    Suggest matching tags/categories based on the content, grabs a list of existing terms from the site and passes it to the prompt together with the post content.

    Sentiment analysis for content / comments

    Using a simple prompt to say whether the text is positive or negative. Could be used to suggest rephrasing the text à la Grammarly, or identify negative comments.

    Summary

    From summarizing content to more complex examples like automatically categorizing posts, the currently explored use cases peek at what’s possible with Web AI in WordPress. Their main advantage lies in their combined strength and the deep integration into WordPress, making for a seamless UX. Chrome’s built-in AI also makes this functionality ubiquitous, as every WordPress user could leverage it without any browser extension, plugin, or API. This is just the beginning. In the future, more complex AI features could push the boundaries of interacting with WordPress even further.

    1. I’m of course talking about client-side media processing. ↩︎
  • Client-side media processing in WordPress

    Client-side media processing in WordPress

    At WordCamp US 2024 I gave a presentation about client-side media processing, which is all about bringing WordPress’ media uploading and editing capabilities from the server to the browser. Watch the recording or check out the slides. This blog post is a written adaption of this talk.

    This was a long overdue step after my announcement tweet in December 2023 went viral. After that, I talked about it on the WP Tavern Jukebox podcast and in an interview with WPShout. You might also remember an old post I wrote about client-side video optimization. This is a 10x evolution of that.

    A lot has changed since then. Not only have I built new features, but I also completely refactored the Media Experiments plugin that was all part of. For WordCamp US, I chose to put more focus on the technical aspects of media handling in WordPress and the benefits of a new browser-based approach.

    If you haven’t seen it before, here’s a quick glimpse of what browser-based media processing allows us to do:

    Contributors wanted

    You can find everything covered in this article in the Media Experiments GitHub repository. It contains a working WordPress plugin that you can easily install on your site or even just test with one click using WordPress Playground.

    The goal is to eventually bring parts of this into WordPress core itself, which is something I am currently working on.

    To make this project a reality, I need your help! Please try the plugin and provide feedback for anything that catches your eye. Or even batter, check out the source code and help tackle some of the open issues.

    Let WordPress help you

    The WordPress project has a clear philosophy, with pillars such as “Design for the majority” and “Decisions, not options”. There is one section in that philosophy which particularly stands out to me:

    The average WordPress user simply wants to be able to write without problems or interruption. These are the users that we design the software for

    WordPress philosophy

    In my experience, when it comes to uploading images and other types of media, WordPress sometimes falls short of that. There are still many problems and interruptions. This is even more problematic nowadays as the web is more media-rich than ever before.

    Minimizing frustration

    Who hasn’t experienced issues when uploading media to WordPress?

    Perhaps an image was too large and uploading takes forever, maybe even resulting in a timeout. And if it worked, the image was so large that it degraded your site’s performance.

    Maybe you were trying to upload a photo from your phone, but WordPress doesn’t support it and tells you to please use another file. Or even worse, you upload a video, it succeeds, but then you realize none of the browsers actually support the video format.

    In short: uploading media is a frustrating experience.

    To work around these issues, you start googling how to manually convert and compress images before uploading them. Maybe even reducing the dimensions to save some bandwidth.

    If you use videos, maybe you upload them to YouTube because you don’t want to bother with video formats too.

    Maybe you switch your hosting provider because your server still takes too long to generate all those thumbnails or because it doesn’t support the newest image formats.

    This is tedious and time consuming. WordPress should be taking work off your shoulders, not making your lives harder. So I set out to make this better. I wanted to turn this around and let WordPress help you.

    At State of the Word 2023, WordPress co-founder Matt Mullenweg said the following about WordPress’ mission to Democratize Publishing:

    We take things that used to require advanced technical knowledge and make it accessible to everyone.

    Matt Mullenweg

    And I think media uploads is a perfect opportunity for us to apply this. And the solution for that lies in the browser.

    WebAssembly

    Your server might not be capable to generate all those thumbnails or to convert a specific image format to something usable. But thanks to your own device’s computing power and technologies such as WebAssembly, we can fix this for you.

    With WebAssembly you can compile code written in a language like Rust or C++ for running it in browsers with near-native performance. In the browser you can load WebAssembly modules via JavaScript and seamlessly send data back and forth.

    At the core of my what I am showing you here is one such WebAssembly solution called wasm-vips. It is a port of the powerful libvips image processing library. That means any image operation that you can do with vips, you can now do in the browser.

    Vips vs. ImageMagick

    Vips is similar to ImageMagick, which WordPress typically uses, but has some serious advantages. For example, when WordPress loads vips in the browser it can always use the latest version. Whereas on the server we have to use whatever version that is available.

    Sometimes, those are really old versions that have certain bugs or don’t support more modern image formats like AVIF. For hosts it can be challenging to upgrade, as they don’t want to break any sites. And even if ImageMagick already supports a format like AVIF, it could be very slow. Vips on the other hand is more performant, has more features, and even for older formats like JPEG it uses newer encoders with better results.

    Client-side vs. server-side media processing

    Sequence diagram for server-side media processing
    Sequence diagram for server-side media processing

    Traditionally, when you drop an image into the editor or the media library, it is sent to WordPress straight away. There, ImageMagick creates thumbnails for every registered image size one by one. That means a lot of waiting until WordPress can proceed. Here is where timeouts usually happen.

    Eventually, once all the thumbnails are generated, WordPress creates a new attachment and sends it back to the editor. There, the editor can swap out the file you originally dropped with the final one returned by the server.

    Compare this to the client-side approach using the vips image library:

    Sequence diagram for client-side media processing
    Sequence diagram for client-side media processing

    Once you drop an image into the editor, a web worker creates thumbnails of it. A web worker runs in a separate thread from the editor, so none of the image processing affects your workflow. Plus, the cropping happens in parallel, which makes it a super fast process. Every thumbnail is then uploaded separately to the server. The server only has to do little work, just storing the file and returning the attachment data.

    You immediately see all the updates in the editor after every step, so you have a much faster feedback loop. With this approach, the chances for errors, timeouts or memory issues are basically zero.

    New use cases

    The Media Experiments plugin contains tons of media-related features and enhancements. In this section I want to highlight some of them to better demonstrate what this new technology unlocks in WordPress.

    Image compression

    As shown in the demo at the beginning of the article, a key feature is the ability to compress or convert images directly in the browser. This works for existing images as well as new ones. All the thumbnails are generated in the browser as well.

    Bonus: Did you see it? The plugin automatically adds AI-generated image captions and alt text for the image. This simply wouldn’t be possible on a typical WordPress server, but thanks to WebAssembly we can easily use AI models for such a task in the browser.

    You can also compress all existing images in a blog post at once. The images can come from all sorts of sources too, for example from the image block, gallery block, or the post’s featured image.

    In theory you could even do this for the whole media library. The tricky part of course is that your browser needs to be running. So that idea isn’t fully fleshed out yet.

    Smart thumbnails

    By default, when WordPress creates those 150×150 thumbnails it does a hard crop in the center of the image. For some photos that will lead to poor results where for example it cuts off the most relevant part of the picture, like a person’s head.

    Vips supports saliency-aware image cropping out of the box, which looks for things like color saturation to determine a better crop.

    Comparison of default vs. smart image cropping
    Comparison of default vs. smart image cropping

    At first you might think it is just a minor detail, but it’s actually really impactful. It just works, and it works for everybody! You will never have to worry about accidentally cropping off someone’s face again.

    HEIC Images

    If you use an iPhone you might have seen HEIC/HEIF images before, as it uses that format by default. It is a format with strong compression, but only Safari fully supports it.

    Thanks to WebAssembly, WordPress can automatically convert such images to something usable. In this demo you will first notice a broken preview, as the (Chrome) browser doesn’t support the file format. But then it swiftly converts it to a JPEG, fixing the preview, and then uploads it to the server.

    Bonus: this also works for JPEG XL, which is another format that only Safari supports.

    Upload from your phone

    In the above video I used an HEIC image which I previously took on my iPhone and then transferred to my computer. And from my computer I then uploaded it to WordPress. But what if you cut out the middleman?

    In the editor, you can generate a QR code that you scan with your camera, or a URL that you can share with a colleague. Here, I am opening this URL in another browser, but let’s pretend it’s my phone. On your phone you then choose the image you want to upload. After that, it magically appears in the editor on your computer.

    Hat tip to John Blackbourn for the idea!

    Video compression

    Media compression and conversion also works great for videos. When I record screencasts for this post, they will be in the MOV format, which doesn’t work in all browsers.

    Thanks to ffmpeg.wasm, a WebAssembly port of the powerful FFmpeg framework, WordPress can convert them to a more universal format like MP4 or WebM. The same works for audio files as well.

    This solution also generates poster images out of the box, which is important for user experience and performance.

    Bonus: just like for image captions, AI can automatically subtitles for any video.

    Animated GIFs

    Sometimes you’re not dealing with videos though, but with GIFs. Who doesn’t like GIFs?

    Well, the thing is, GIFs are actually really bad for user experience and performance. Not only are they usually very bad quality, they can also be huge in file size. Most likely you should not be using animated GIFs.

    Every time you use the GIF format, a kitten dies
    As Paul Bakaus once said: Gifs must die

    The good news is that animated GIFs are nothing but videos with a few key characteristics:

    • They play automatically.
    • They loop continuously
    • They’re silent.

    By converting large GIFs to videos, you can save big on users’ bandwidth. And that’s exactly what WordPress can and should do for you.

    In the following demo, I am dragging and dropping a GIF file from my computer to the editor. Since it is an image file, WordPress first creates an image block and starts the upload process.

    Then, it detects that it is an animated GIF, and uses FFmpeg to convert it to an MP4 video. This happens in the blink of an eye. As it’s now a video, WordPress replaces the image block with a special GIF variation of the video block that’s looping and autoplaying. And of course the video is multiple times smaller than the original image file. As a user, you can’t tell the difference. It just works.

    Media recording

    Compressing videos and converting GIFs is cool, but one of my personal favorites is the ability to record videos or take still pictures directly in the editor, and then upload them straight to WordPress.

    So if you’re writing some sort of tutorial and want to accompany it with a video, or if you are building the next TikTok competitor, you could do that with WordPress.

    Bonus: You probably don’t see it well in the demo, but thanks to AI you can even blur your background for a little more privacy. Super cool!

    Challenges

    Client-side media processing adds a pretty powerful new dimension to WordPress, but it isn’t always as easy as it looks!

    Cross-origin isolation

    On the implementation side, cross-origin isolation is a tricky topic.

    So, WebAssembly libraries like vips or ffmpeg use multiple threads to speed up the processing, which means they require shared memory. Shared memory means you need SharedArrayBuffer.

    For security reasons, enabling SharedArrayBuffer requires a special configuration called cross-origin isolation. That puts a web page into a special state that enforces some restrictions when loading resources from other origins.

    In the WordPress editor, I tried to implement this as smoothly as possible. Normally, you will not even realize that cross-origin isolation is in effect. However, some things in the editor might not work as expected anymore.

    The most common issue I encountered is with embed previews in the editor.

    So in Chrome, all your embed previews in the editor continue to work, while in Firefox or Safari they don’t because they do not support iframe credentialless when isolation is in effect.

    I hope that Firefox and Safari remedy this in the future. Chrome is also working on an alternative proposal called Document-Isolation-Policy which would help resolve this as well. But that might still be years in the future.

    Open source licenses (GPL compatibility)

    Another unfortunate thing is that open source licenses aren’t always compatible with each other. This is the case with the HEIC conversion for those iPhone photos.

    Being able to convert those iPhone photos directly in the browser before sending them to the server just makes so much sense. Unfortunately, it’s a very proprietary file format. The only open source implementation (libheif) is licensed under the LGPL 3.0, which is only compatible with GPL v3. However, WordPress’ license is GPLv2 or later.

    That means we can’t actually use it 🙁

    The good news is that we found another way, and it’s even already part of the next WordPress release!

    However, this happens on the server again instead of the browser.

    This is possible because on the server the conversion happens in ImageMagick (when compiled with libheif), and not in core itself, so there’s no license concern for WordPress.

    The downside of this approach is that it will only work for very few WordPress sites, as it again depends on your PHP and ImageMagick versions. So while this is a nice step into the right direction, only with the client-side approach can we truly support this for everyone.

    The next steps

    All of these challenges simply mean there is still some work to do before it can be put into the hands of millions of WordPress users.

    While this project started as a separate plugin, I am currently in the process of contributing these features step by step to Gutenberg, where we can further test them behind an experimental flag.

    We start with the fundamental rewrite of the upload logic, adding support for image compression and thumbnail conversion. After that, we can look into format conversion, making it easier to use more modern image formats and choosing the format that is most suitable for any given image. From there, we can expand this to videos and audio files.

    Finally and ideally, we expand beyond the post editor and make this available to the rest of WordPress, like the media gallery or anywhere else where one would upload files.

    I am also really excited about the possibility of making this available to everyone building a block editor outside of WordPress, like Tumblr for example.

    Democratizing publishing

    With client-side media processing we make a giant leap forward when it comes to democratizing publishing.

    As mentioned at the beginning, the average WordPress user simply wants to be able to write without problems or interruption. By eliminating all these problems related to media, users will be able to create media-rich content much easier and faster.

    Thanks to client-side media processing, we can greatly improve the user experience around uploads. You benefit from faster uploads, fewer headaches, smaller images, and less overloaded servers. Also, you no longer need to worry about server support or switch hosting providers. Smaller images and more modern image formats help make your site load faster too, which is a nice little bonus.

    Convinced? Check out the GitHub repository and the proposed roadmap for the Gutenberg integration.

  • Automated testing using WordPress Playground and Blueprints

    Automated testing using WordPress Playground and Blueprints

    Learn how to leverage WordPress Playground and Blueprints for automated end-to-end browser and performance testing.

    Late last year I published a detailed tutorial for getting started with end‑to‑end performance testing in WordPress. It was accompanied by a template GitHub repository and a dedicated GitHub Action for effortlessly running performance tests with zero setup.

    Introductory blog post to browser-based performance testing in WordPress projects

    While that GitHub Action works extremely well, the zero-setup approach has two drawbacks:

    1. It is not possible to configure the test environment, for example by adding demo content or changing plugin configuration
    2. It is not possible to test more complex scenarios, like any user interactions (e.g. for INP)

    For (2) the best alternative right now is to go with the manual approach. For (1), I have now found a solution in WordPress Playground. Playground is a platform that lets you run WordPress instantly on any device. It can be seen as a replacement for the Docker-based @wordpress/env tool.

    A playground set with a slide and a stairway. There are trees in the background.
    A real playground but just as fun! Photo by Hudson Roseboom on Unsplash

    Using Blueprints for automated testing

    One particular strength of WordPress Playground is the idea of Blueprints. Blueprints are JSON files for setting up your WordPress Playground instance. In other words, they are a declarative way for configuring WordPress—like a recipe. A blueprint for installing a specific theme and plugin could look like this:

    {
    	"steps": [
    		{
    			"step": "installPlugin",
    			"pluginZipFile": {
    				"resource": "wordpress.org/plugins",
    				"slug": "performance-lab"
    			}
    		},
    		{
    			"step": "installTheme",
    			"themeZipFile": {
    				"resource": "wordpress.org/themes",
    				"slug": "twentytwentyone"
    			}
    		}
    	]
    }Code language: JSON / JSON with Comments (json)

    Performance testing 2.0

    The newly released version 2 of the performance testing GitHub Action now uses Blueprints under the hood to set up the testing environment and do things like importing demo content and installing mandatory plugins and themes. In addition to that, you can now use Blueprints for your own dedicated setup!

    This way you can install additional plugins, change the site language, define some options, or even run arbitrary WP-CLI commands. There are tons of possible steps and also a Blueprints Gallery with real-world code examples.

    To get started, add a new swissspidy/wp-performance-action@v2 step to your workflow (e.g. .github/workflows/build-test.yml):

    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Run performance tests
        uses: swissspidy/wp-performance-action@v2
        with:
          urls: |
            /
            /sample-page/
          plugins: |
            ./my-awesome-plugin
          blueprint: ./my-custom-blueprint.json
          iterations: 5
          repetitions: 1Code language: YAML (yaml)

    Then, add the blueprint (my-custom-blueprint.json):

    {
      "$schema": "https://playground.wordpress.net/blueprint-schema.json",
      "plugins": [
        "performant-translations",
        "akismet"
      ],
      "steps": [
        {
          "step": "defineWpConfigConsts",
          "consts": {
            "WP_DEBUG": true
          }
        },
        {
          "step": "activatePlugin",
          "pluginName": "My Awesome Plugin",
          "pluginPath": "/wordpress/wp-content/plugins/my-awesome-plugin"
        }
      ]
    }Code language: JSON / JSON with Comments (json)

    And that’s it!

    The GitHub Action will now use your custom blueprint to install and activate your own custom plugin and performance-lab and akismet plugins from the plugin directory.

    Alongside this new feature I also included several bug fixes for things I originally planned to add but never really finished. For instance, it is now actually possible to run the performance tests twice and then compare the difference between the results.

    This way, when you submit a pull request you can run tests first for the main branch and then for your PR branch to quickly see at a glance how the PR affects performance. Here is an example:

    jobs:
      comparison:
        runs-on: ubuntu-latest
    
        steps:
    
        # Check out the target branch and build the plugin
        # ...
    
        - name: Run performance tests (before)
          id: before
          uses: ./
          with:
            urls: |
              /
              /sample-page/
            plugins: |
              ./tests/dummy-plugin
            blueprint: ./my-custom-blueprint.json
            print-results: false
            upload-artifacts: false
    
        # Check out the current branch and build the plugin
        # ...
    
        - name: Run performance tests (after)
          uses: ./
          with:
            urls: |
              /
              /sample-page/
            plugins: |
              ./tests/dummy-plugin
            blueprint: ./my-custom-blueprint.json
            previous-results: ${{ steps.before.outputs.results }}
            print-results: true
            upload-artifacts: falseCode language: PHP (php)

    The result will look a bit like this:

    Screenshot of the performance tests results printed in a GitHub Actions workflow summary, comparing metrics such as LCP or memory usage before and after a change.
    Example workflow summary when comparing two sets of performance testing results.

    Playground is the future

    Being able to use Playground for automated testing is really exciting. It simplifies a lot of the setup and speeds up the bootstrapping, even though the sites themselves aren’t as fast (yet) as when using a Docker-based setup. However, there is a lot of momentum behind WordPress Playground and it is getting better every day. Applications like this one further help push its boundaries.

    I had similar success so far when testing Playground with our WordPress performance comparison script and I think it could work well for the Plugin Check GitHub Action.

    WordPress Playground clearly is the future.

  • How I commit to WordPress core

    How I commit to WordPress core

    After WordCamp US 2024, some core committers have started sharing their WordPress contribution workflows. Since mine appears to be a bit different from the other ones posted so far, I figured I’d follow suit. So here’s how I commit to WordPress!

    The following sections cover everything that comes to mind right now. If there’s more in the future or something changes, I’ll try to update this post accordingy.

    Separate repositories

    I use separate folders for the checkouts of the Git repository and the SVN repository. The Git repository points to both official mirrors.

    For daily work, I use Git. Be it for the local development environment, running tests, applying patches, or submitting pull requests.

    Only for committing I take an actual patch file or PR and apply it in the SVN repository and then commit the change.

    Local development

    On my work laptop I cannot use Docker, so I can’t use the built-in development environment. Instead I use Local for running trunk locally. I symlinked the default site it creates to my Git checkout, which worked surprisingly well.

    Aliases

    Over the years my workflow hasn’t changed that much, so for the most frequently used commands I created aliases and put them in my dotfiles.

    Aliases for Subversion:

    alias sup="svn up --ignore-externals"
    alias vcsclean="find . -name \"*.orig\" -delete && find . -name \"*.rej\" -delete"
    alias svnclean="svn revert -R * && vcsclean && sup"Code language: Bash (bash)

    For Git I have a ton more aliases, but nowadays I only use a handful, like git squash, git amend, git undo, or git patch.

    I might add the gh-patch alias Joe uses, as it is faster than using npm run grunt patch, especially since the latter always tries to first install npm dependencies, which is a bit annoying.

    Committing

    If I am committing on the command line, I use svn ci to open my default editor, which is nano. There I write the commit message, save the changes, and close the editor to push the commit.

    I check the commit message guidelines frequently, which I think every core committer should do. Sometimes I think a custom linter in a pre-commit hook or so would be cool.

    GUI

    Most of the time I actually don’t use svn on the command line, but instead use Cornerstone, which as a GUI Subversion client for Mac. I think it was originally recommended to me by my friend and fellow core committer Dominik.

    A graphical user interface provides this extra safety when dealing with more complex scenarios like adding or removing files, backporting commits, or changing file properties. On the command line it would easy to miss some things, but with a UI it’s much easier to see everything at a glance.

    Similarly, I use Tower for Git.

  • Identifying WordPressers on GitHub

    Identifying WordPressers on GitHub

    When contributing to WordPress core or related projects, a lot of the time is spent between WordPress Trac and GitHub. You typically open a new Trac ticket to propose an enhancement, then submit a pull request on GitHub with the necessary code changes. You may then even use Slack to discuss the change with fellow contributors. That’s now three platforms to participate in WordPress development—and three usernames to keep juggling between.

    Some people (myself included) use the same username on all platforms, while others have completely separate ones on each of them. That makes it very inconvenient to track down people, for example when you want to chat with a contributor on Slack, or want to give a pull request author props in a Subversion commit. Luckily, the latter has become a bit easier thanks to Props Bot. Still, I regularly found myself using site:profiles.WordPress.org <username> Google searches to find someone’s WordPress.org account based on their GitHub information. Surely there is a better way to do this.

    Screenshot of the WordPress.org user profile of Isabel Brison, which lists their separate usernames on the two platforms.
    An example of a WordPress contributor with different usernames on these platforms. Isabel is @isabel_brison on WordPress.org and @tellthemachines on GitHub.

    WordPressers on GitHub browser extension

    Luckily, I found out about an API to get someone’s WordPress.org username based on their GitHub username. That’s exactly what I needed! Using this API, I built a very straightforward WordPressers on GitHub browser extension. This extension displays a WordPress logo next to a GitHub username if the person has a username on WordPress.org. Simply click on the logo to visit their profile, or hover over it to see a tooltip with their username.

    Screenshot of the WordPressers on GitHub extension in action, showing a WordPress logo and tooltip next to their username

    Thanks to a great suggestion by Jonathan Desrosiers, the WordPress.org username is also automatically displayed in the bio when viewing a GitHub profile.

    A screenshot of a GitHub user profile, which now automatically lists someone's WordPress.org username as well

    In the few weeks I’ve been using the extension, it has already come in handy a lot of times. Especially since I was a core tech lead for the WordPress 6.5 release and reviewing lots of pull requests during that time. May it be useful for you too 🙂

    Right now, WordPressers on GitHub is available for both Chrome and Firefox:

  • Performance for Everyone: Democratizing Performance in WordPress

    Performance for Everyone: Democratizing Performance in WordPress

    At WordCamp Asia 2024 I had the opportunity to talk about the work I’ve been doing as part of the WordPress core performance team. The recordings are not available yet, but you can re-watch the livestream and check out the slide deck in the meantime. This blog post summarizes my presentation and amends it with further information and links to relevant resources.

    In the style of the WordPress mission statement to democratize publishing, I like to call this effort Democratizing Performance. Or in other words: performance for everyone. In my eyes, everyone should be able to have a fast website, regardless of their skill level or technical knowledge. To achieve this, we take things that used to require advanced technical knowledge and make it accessible to everyone.

    Why does performance matter, you ask? Performance is essential for a great user experience on the web, and poor performance can negatively hurt your business. A slow website can lead to visitors leaving and not coming back. If your checkout process is slow, users might not end up buying products in your online store. With performance being a factor for search engines, it can also affect your site’s ranking.

    There are many different aspects to performance within WordPress. Here, I am usually referring to the performance of the frontend of your website, as measured using metrics such as Core Web Vitals. Core Web Vitals are a set of performance metrics that measure loading performance, interactivity, and layout stability. WordPress has been working to improve its performance, and Core Web Vitals are a great way to measure that progress.

    The WordPress core performance team was founded a few years ago. It is dedicated to monitoring, enhancing, and promoting performance in WordPress core and its surrounding ecosystem. Having a dedicated team for this kind of effort shows that the community understands the rising complexity of today’s websites. This way. WordPress is well-equipped to cater for these use cases in a performant way.

    The team’s activities can be roughly grouped into three categories:

    1. Improving core itself, providing new APIs, fixing slow code and measuring improvements
    2. Working with the ecosystem to help people adopt best practices and make their projects faster
    3. Providing tools and documentation to facilitate doing so.

    Tackling performance in an open source project like WordPress involves more than improving the core software itself. This is different from closed platforms, where you don’t have to worry about elevating an entire ecosystem with thousands of plugins and themes. Democratizing performance is not something that WordPress or the core performance team can do alone. It takes all of us, including site assemblers and extenders, to work together to raise the bar for everyone.

    Recent core performance improvements

    Still, there are some things we can do in core itself. Despite the performance team’s young age it already has a proven track record of performance enhancements. To name a few:

    • Automated performance testing using Playwright, feeding metrics into a public dashboard
    • Improvements to image lazy loading. This includes adding the fetchpriority attribute to the image which is likely to be the LCP element.
    • Improve emoji support detection

    In my talk, I highlighted the emoji change because it’s such a great example of improving performance for everyone. Ever since WordPress added emoji support 10 years ago, it loads a little bit of JavaScript on every page to see whether your browser supports the latest and greatest emoji (there are new ones almost every year). It turns out that doing so on every page load is quite wasteful — who would have thought!

    Fortunately, there are better ways to do this. Since last year, this emoji detection happens only once per visit, caching results for subsequent visits in the same session. Additionally, the detection now happens in a web worker, keeping the main thread free for more important work. This change was the main contributing factor to a ~30% client-side performance improvement in WordPress 6.3, compared to WordPress 6.2. To benefit from this, all you had to do was update your website — a great example of performance for everyone.

    Measuring success

    Such impressive numbers are testament to the focus on data-driven decision making in WordPress. It’s important to base our work on actual numbers rather than a gut feeling. Getting these numbers is a two-fold process.

    1. Reproducible, automated testing in a controlled environment where you measure the desired metrics for each code change to verify improvements. Also known as lab testing or lab data.
    2. Measuring how WordPress performs for millions of actual sites in the world. This is so-called field data.

    This kind of field data is available for free through datasets such as HTTP Archive and the Chrome UX Report. The latter is the official dataset of the Web Vitals program. All user-centric Core Web Vitals metrics are represented there. These datasets provide a more accurate picture of WordPress performance in the wild, so this is the data we want to positively influence. The report on WordPress performance impact on Core Web Vitals in 2023 covers some recent highlights in this regard.

    WordPress performance in 2024

    The improvements in the last couple of years were already pretty impressive. Still, the performance team continues working hard on even further improvements. The current year is still relatively young, but there are already some exciting new changes in the works:

    • Performant translations
      Previously, localized WordPress sites could be up to 30% slower than a regular site. Thanks to a new translation library in WordPress 6.5 this difference is now almost completely eliminated.
    • Interactivity performance
      Interaction to Next Paint (INP) is now officially a Core Web Vital. Interactivity performance is therefore top of mind for our team. We’re currently identifying common key problems and opportunities to improve interactivity bottlenecks in WordPress. We are also spreading the word about the new interactivity API in WordPress 6.5.
    • Modern image formats
      Now that all major browsers understand the format, WordPress 6.5 adds AVIF support.
    • Client-side image processing
      You might have heard about my media experiments work already. Essentially, we want to bring most of the image processing in WordPress from the server to the client. This enables exciting things like image optimization or AVIF conversion directly in the browser, regardless of what server you are on.
    • Speculative page prerendering
      There is now a new feature plugin that adds support for speculative prerendering for near-instant page loads. This is a new browser API for prerendering the next pages in the background, for example when hovering over a link. This is a great example of how WordPress can embrace the web platform and provide native support for new APIs to developers.

    Check out the core performance team’s roadmap for this year to learn more about these endeavors.

    Ecosystem & tooling

    As I said in the beginning of this article, democratizing performance involves more than making WordPress itself faster. It’s the responsibility of site builders and developers too. That’s why we try to help the ecosystem track performance and adopt new features we build. Be it through WordCamp talks like this one or more advanced documentation. My recent blog posts on WordPress performance testing and the Plugin Check plugin are great examples of that effort. Both tools are also available as GitHub Actions, making it really easy to get started.

    Performance Lab is also a great tool for us to improve performance well beyond WordPress core. If you haven’t heard about it yet, Performance Lab is a collection of performance-related feature plugins. It allows you to test new features before they eventually end up in a new WordPress release. This way, the team can validate ideas at a larger scale and iterate on them more quickly. Once they are stable, we can propose merging them into core. Or, sometimes we find that a particular enhancement doesn’t work that well, so we discard it.

    Screenshot of the Performance Lab plugin page. The plugin allows testing new performance features ahead of time.

    Dreaming bigger

    As you can see, there is a lot going on already in the performance space. But what if we go further than that? What if we dream bigger? I want to spark your imagination a little bit by thinking about some other exciting things that we could build. For example, there could be a performance section in Query Monitor. Or imagine your browser’s developer tools not only telling how to improve your slow JavaScript, but also how to do it in the context of WordPress.

    Platforms like WP Hive and PluginTests.com are also promising. They basically test all plugins in the entire plugin directory to see if they work as expected. They also measure memory usage and performance impact on both the frontend and backend. Thanks to a browser extension, this information is surfaced directly inside the plugin directory. Ironically, we actually already have the infrastructure available on WordPress.org to do this ourselves. Tide was built exactly for this purpose. Unfortunately the project has stalled since its original inception 6 years ago, but what if it came back?

    Screenshot of the checks performed by tools like WP Hive. They highlight a plugin's performance impact on a site.
    Tools like WP Hive highlight a plugin’s impact on a site

    Finally, how does artificial intelligence fit into this? Of course in 2024 you kind of have to mention AI one way or the other. Imagine an AI assistant in your WordPress admin that tells you how to optimize your site’s configuration. Or the assistant in your code editor having deep knowledge of the latest WordPress APIs and telling you how to load your plugin’s JavaScript in a more efficient way. You don’t have to be an expert to benefit from such helpers, making it possible for everyone to have a fast website.

    Conclusion

    Performance is a critical factor for any website. WordPress is committed to making good performance accessible to everyone, and the results are showing. Still, there is a lot of work to be done and a lot of opportunities to improve performance at scale. The core performance team can’t do this alone. It takes all of us — site owners, site builders, developers, agencies — to make the web a better — and faster — place.

    Together, we can democratize performance.