WordPress
WordPress HTML API for product catalogue markup in 2026
The WordPress HTML API is how I now fix product catalogue markup on WooCommerce storefronts without regex that breaks the first time a merchant pastes a < in a spec table. I am Alan Vo, a Gold Coast web developer with 18 years on business sites and shops. In 2026 the HTML API is not a niche core experiment. WordPress 6.9 tightened wp_kses(), refactored classic image blocks through WP_HTML_Tag_Processor, and made serialize_token() public on WP_HTML_Processor. That is the stack I reach for when a PDP template, a third-party snippet, or an old shortcode leaves broken tags in the_content.
This note is for anyone who already ships a WordPress block theme or a hybrid classic theme with WooCommerce templates, and who is tired of preg_replace on rendered HTML. It pairs with block bindings on product pages for data, and with the Interactivity API for client behaviour. The HTML API is the safe server-side editor for the string those layers sit on.
Why the WordPress HTML API matters for catalogue HTML
The WordPress HTML API matters for catalogue HTML because product pages are messy fragments, not tidy documents. A furniture PDP might embed a size chart block, an iframe from a manufacturer, and a merchandising paragraph with inline bold. A footwear PDP might wrap gallery markup from a plugin with its own div soup. Regex assumes you know every edge case. The HTML API walks tokens, aborts on unsupported markup instead of corrupting it, and lets you change attributes or extract subtrees in place.
Core documents two entry points in the WP_HTML_Tag_Processor reference and the WP_HTML_Processor reference. WP_HTML_Tag_Processor finds tags and edits attributes. WP_HTML_Processor extends that with structural awareness: depth, modifiable text, and full-document or fragment parsing. For storefront work I almost always start with WP_HTML_Processor::create_fragment() because block output and the_content are body fragments, not full <html> trees.
WordPress 6.9, described in Updates to the HTML API in 6.9, pushed the API into places merchants already feel. Image handling in classic themes moved off brittle PCRE. get_url_in_content() now uses the Tag Processor and returns a decoded href. wp_kses() gained HTML5 semantic tags and safer handling of numeric character references for users without unfiltered_html. That last point matters on multi-author catalogues where junior staff paste spec copy from PDFs.
I do not use the HTML API to replace WooCommerce templates. I use it on the rendered output when a plugin upgrade, a migration, or a bad embed leaves one broken attribute on every SKU page. The goal is the same as good caching: fix the HTML once at render time, ship valid markup to crawlers and to the browser.
How the WordPress HTML API works on storefront fragments
The WordPress HTML API works on storefront fragments by tokenizing HTML5 in UTF-8, matching tags with queries, and returning updated HTML through get_updated_html(). Fragment mode is the default mental model. You pass the string you would echo inside a product layout, not the whole theme shell.
A minimal attribute fix looks like this:
``php $processor = WP_HTML_Processor::create_fragment( $html ); while ( $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) { if ( ! $processor->get_attribute( 'loading' ) ) { $processor->set_attribute( 'loading', 'lazy' ); } if ( ! $processor->get_attribute( 'decoding' ) ) { $processor->set_attribute( 'decoding', 'async' ); } } $html = $processor->get_updated_html(); ``
That pattern is boring on purpose. It is safer than appending loading="lazy" with a regular expression that also hits a JSON-LD script by mistake. The Tag Processor documentation warns that it pauses on incomplete tokens at end of document rather than guessing. On partial AJAX responses you must pass complete fragments.
When I need structure, not just attributes, I stay on WP_HTML_Processor. WordPress 6.9 made serialize_token() public so you can copy normalized outer HTML for matched elements without hand-building closing tags. The dev note shows extracting every paragraph by walking depth and serializing tokens. I use the same idea to lift a manufacturer embed block out of post content for a sidebar slot, or to strip duplicate H2 headings a migration plugin inserted above the price block.
Custom data attributes are the bridge to JavaScript on the storefront. HTML uses data-* names; browsers expose them on element.dataset with a different spelling. WordPress 6.9 added wp_js_dataset_name() and wp_html_custom_data_attribute_name() so PHP that writes attributes matches what the Interactivity API or a small vanilla script reads. I call those helpers whenever I add data-wp-interactive or a product-line flag next to add to cart, instead of guessing whether camelCase belongs in the HTML string.
Full-document mode via WP_HTML_Processor::create_full_parser() exists for imports and email templates. I rarely use it on live PDP routes because fragment mode matches WooCommerce loops. If you must parse a full static HTML dump from a legacy catalogue export, convert encoding to UTF-8 first. The class reference is explicit that UTF-8 is the supported encoding.
Unsupported markup stops the processor early. That is a feature. If your fragment contains table nesting a plugin generated incorrectly, you get a failed walk, not a silently truncated page. Log the slug, fix upstream, or fall back to the unmodified string. Never chain regex after a failed processor pass hoping to "finish the job".
Production checklist for WordPress HTML API on product pages
A WordPress HTML API pass on product pages is production-ready when it fails closed, runs once per request, and respects who is allowed to change HTML.
- Confirm WordPress 6.7 or newer for
WP_HTML_Processor::create_fragment(). Target 6.9 on catalogue sites so you inherit thewp_kses()and image refactors tied to the HTML API. - Hook after the content you need exists:
the_contentfilter at a priority that runs after shortcodes and blocks render, or a WooCommerce template hook that receives the final gallery HTML string. - Use
create_fragment()for block and product description output. Reserve full parser mode for offline tools. - Guard expensive walks: skip admin, skip feeds, skip REST unless you are transforming a dedicated field. Cache the result in a transient keyed by product ID and content hash if the walk is heavy.
- Query narrowly. Pass
tag_name,class_name, ormatch_offsettonext_tag()instead of scanning every token when you only care aboutIMGinside.woocommerce-product-gallery. - Use
set_attribute()for lazy loading,sizes,fetchpriorityon the LCP image, andrelon outbound spec links. Do not strip classes WooCommerce JS depends on. - Map data attributes with
wp_html_custom_data_attribute_name()when pairing PHP output with storefront scripts. - If you modify text nodes, remember
set_modifiable_text()onSCRIPTrejects strings that look like nested script tags. That protects you from accidental script injection when cleaning embed codes. - Compare before and after HTML on staging SKUs with tables, iframes, and shortcodes. Include a variable product and a grouped product.
- Add a unit or integration test that uses semantic HTML comparison when your project has PHPUnit. WordPress 6.9 introduced
assertEqualHTML()on the test base class for that reason.
I keep one staging product with an intentionally broken embed. If a deploy regresses the processor, that SKU shows it before Google Search Console does.
What breaks when you patch catalogue HTML with regex instead of the WordPress HTML API
What breaks is predictable. Regex on the_content is fast until it is not. I have unwound these failures on live catalogues.
The first break is double transformation. A filter adds loading="lazy" to every img, then a performance plugin does the same. Crawlers see duplicated attributes or invalid HTML. The HTML API lets you test get_attribute( 'loading' ) before you set it.
The second break is script and JSON-LD damage. Product schema and GA4 snippets live beside gallery markup. A naive pattern that touches anything between < and > can corrupt a JSON-LD block and silently drop rich results. The Tag Processor's scripting flag and token boundaries exist so you skip or special-case SCRIPT content.
The third break is merchant content. A spec table with < in a cell, or an attribute value with quotes, is valid in context. Regex replacements often truncate the string and white-screen the PDP for one SKU. The HTML API aborts instead of publishing truncated HTML.
Fourth, encoding and entity confusion. Pasting Windows-1252 copy into UTF-8 WordPress is common on catalogue migrations. Feeding non-UTF-8 bytes into the processor produces wrong tokens. Fix encoding at import, then run the API.
Fifth, fighting wp_kses(). Authors without unfiltered_html rely on kses on save. Transforming on display does not replace sanitization on input. WordPress 6.9 changed kses behaviour around numeric references; retest comment and spec fields after upgrade.
If get_updated_html() returns unexpected output, dump whether next_tag() reached your target, whether the processor bailed early, and whether a caching layer serves an old string. Object cache plugins that cache full page HTML hide filter bugs until purge.
How to measure WordPress HTML API changes on a storefront
Measure whether HTML is valid, equivalent, and faster to maintain, not whether a one-off regex line count shrank.
I run the same product URL through the W3C validator or an equivalent linter on staging before and after the filter. One error on one SKU is enough to block deploy. For bulk transforms I sample ten URLs across categories.
In PHPUnit, prefer semantic comparison. WordPress documents assertEqualHTML() in the 6.9 HTML API note so attribute order and quote style do not fail good changes. If you only have manual QA, save rendered HTML to a file and diff in a side-by-side review.
For performance, the HTML API adds CPU on render. Measure with Query Monitor or a simple timer around your filter on a cold cache request. If the walk adds more than a few milliseconds on a heavy PDP, narrow the query or cache the output. The win is usually fewer emergency hotfixes, not raw TTFB drops.
Search Console rich result reports and Merchant Center diagnostics are downstream signals. Fixing broken product markup or spec links shows up as fewer "parse error" warnings over weeks, not overnight conversion spikes. I do not invent conversion percentages for HTML refactors. The honest metric is valid markup on every SKU template and fewer support tickets about "one product page looks wrong".
Related work on this site
The WordPress HTML API shows up wherever catalogue HTML is long-lived and hand-edited. The Skanvi furniture storefront mixes block templates with spec tables that outlive any single plugin version. The BridgeWorx ticketing work embeds third-party widgets that need attribute fixes after upgrades. The Sophia Pro theme project was block-first merchandising where fragment parsing beat another round of regex in functions.php. All were contract development on an agency team. The pattern is the same: render the product, walk the fragment, ship HTML you would be willing to paste into the validator yourself.
FAQ
When should I use WP_HTML_Processor instead of WP_HTML_Tag_Processor on a WooCommerce PDP?
Use WP_HTML_Processor when you need depth, modifiable text, or serialize_token() to copy whole elements. Use WP_HTML_Tag_Processor when you only adjust attributes on matching tags in a flat pass. Most catalogue fixes on the_content start with the Processor in fragment mode because gallery and description markup is nested.
Does the WordPress HTML API replace wp_kses on product descriptions?
The WordPress HTML API does not replace wp_kses(). Kses still sanitizes on save for users without unfiltered_html. The HTML API is for transforming already-sanitized rendered output, such as adding lazy loading or fixing embed attributes. WordPress 6.9 tightened kses using the same HTML stack; test author workflows after core updates.
Can the WordPress HTML API break the Interactivity API on product pages?
The WordPress HTML API can break the Interactivity API if you strip data-wp-interactive, data-wp-context, or data-wp-bind attributes while "cleaning" markup. Query for those attributes and skip those tags, or run your filter before interactivity directives are added. Use wp_html_custom_data_attribute_name() when you add matching data attributes in PHP.
Which WordPress version do I need for serialize_token and dataset helpers on catalogue sites?
You need WordPress 6.7 for early serialize_token() as an internal helper and WordPress 6.9 for public serialize_token(), wp_js_dataset_name(), wp_html_custom_data_attribute_name(), and the core refactors that rely on the Tag Processor for images and links. Plan catalogue upgrades to 6.9 before you depend on those helpers in production theme code.
Keep reading
Contact if you want this kind of work on a live store.