A client’s high-traffic store kept throwing layout errors. The cause was a “clever” snippet that injected custom attributes into product images with preg_replace. It worked fine until someone uploaded an image whose attribute order the regex did not expect, and the layout imploded over a single misplaced double quote.
Years ago my first instinct would have been to “fix” the regex, maybe add a couple more lookarounds. That is just digging a deeper hole. If you parse HTML with regular expressions in 2025, your markup handling breaks the first time someone writes perfectly valid HTML you did not anticipate. The WordPress HTML API improvements in 6.9 remove the last excuse for brittle string manipulation.
serialize_token is now public
The change most of us will actually use is that WP_HTML_Processor::serialize_token() is now public. The processor was always good at finding things, but rebuilding the HTML safely afterwards was awkward. Now you can walk a document and emit normalized HTML for every token you hit, which matters for both security and reliability.
Here is how I fixed that client’s image attribute problem with the updated API. Instead of guessing with regex, WordPress does the normalization.
function bbioon_safe_image_processor( $html ) {
$processor = WP_HTML_Processor::create_fragment( $html );
$output = '';
while ( $processor->next_token() ) {
// If it's an image, let's add our tracking attribute.
if ( 'IMG' === $processor->get_tag() ) {
$processor->set_attribute( 'data-bbioon-track', 'product-view' );
}
// This is the magic. It returns the well-formed representation of the token.
$output .= $processor->serialize_token();
}
return $output;
}
The approach follows the official dev notes. Even when the input HTML is “junk,” the output comes back well-formed, so you stop shipping broken tags.
Mapping HTML attributes to the JavaScript dataset
Anyone who has worked with the Interactivity API or custom blocks knows that the mapping between an HTML attribute like data-wp-bind--class and the JavaScript .dataset.wpBind-class is a headache, and nothing about it is intuitive. 6.9 adds wp_js_dataset_name() and wp_html_custom_data_attribute_name() to do that conversion for you.
That kills the “how many dashes do I need?” guessing game. It is a quiet change, and it is the kind that saves you an hour of debugging on a Friday afternoon.
Semantic testing with assertEqualHTML
Testing gets a fix too. If you write unit tests for your plugins (and you should), you have probably watched assertSame() fail because one string used single quotes and the other used double. That false positive wastes time. The new assertEqualHTML() method in WP_UnitTestClass compares the meaning of the HTML rather than the characters. It knows that <img src="a.jpg" loading="lazy"> is the same as <img loading='lazy' src='a.jpg' />.
This stuff gets complicated fast. If you are tired of debugging someone else’s mess and just want your site to work, drop my team a line. We have probably seen it before. The HTML API can do a lot now, but you still need to use it without overcomplicating your codebase.
Stop fighting HTML with strings. Use the tools Core hands you, and you will spend less of your client’s budget on layout bugs you wrote yourself.