wp-html-api — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited wp-html-api (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill when plugin code needs to read or modify HTML. The goal is to avoid regex-based HTML parsing and unsafe manual escaping. WordPress' HTML API understands malformed real-world HTML better than ad hoc string code and keeps escaping rules in one place.
This skill is not about React, Gutenberg editor internals, or client-side DOM work.
Trigger when ANY of the following is true:
str_replace() to modify HTML tags, attributes, classes, or text nodes.DOMDocument for frontend HTML fragments and then fights encoding, wrapper tags, or HTML5 parsing differences.WP_HTML_Tag_Processor, WP_HTML_Processor, set_attribute, add_class, serialize_token, or data-* attributes.the_content, shortcode output, widget output, email HTML, REST-rendered HTML, or third-party markup.| Task | Prefer |
|---|---|
| Add/remove/read attributes on matching tags | WP_HTML_Tag_Processor |
| Add/remove classes on matching tags | WP_HTML_Tag_Processor |
| Replace text in modifiable text nodes | WP_HTML_Tag_Processor::set_modifiable_text() |
| Traverse nested structure or serialize matched tokens | WP_HTML_Processor |
| Normalize malformed HTML into well-formed HTML | WP_HTML_Processor::normalize() |
Map between data-* HTML names and JS dataset names | wp_js_dataset_name() / wp_html_custom_data_attribute_name() |
For most plugin output filters, start with WP_HTML_Tag_Processor. Reach for WP_HTML_Processor only when you need document/fragment structure, nesting, or token serialization.
function myplugin_add_tracking_attr( string $html ): string {
$processor = new WP_HTML_Tag_Processor( $html );
while ( $processor->next_tag( array( 'tag_name' => 'a' ) ) ) {
$href = $processor->get_attribute( 'href' );
if ( ! is_string( $href ) || ! str_starts_with( $href, 'https://example.com/' ) ) {
continue;
}
$processor->add_class( 'myplugin-tracked-link' );
$processor->set_attribute( 'data-myplugin-source', 'content' );
}
return $processor->get_updated_html();
}Important WP 6.9 behavior: set_attribute() and set_modifiable_text() escape all character references. Pass normal unescaped text. Do not pre-escape with esc_attr(), esc_html(), or htmlspecialchars() before calling these methods, or you will produce double-escaped output.
// WRONG - pre-escaped value can become double-escaped.
$processor->set_attribute( 'title', esc_attr( 'Eggs & Milk' ) );
// RIGHT - pass the raw intended value; HTML API encodes it.
$processor->set_attribute( 'title', 'Eggs & Milk' );set_modifiable_text() only works when the current token is modifiable text. It is not a general "replace all visible text" function.
$processor = new WP_HTML_Tag_Processor( $html );
while ( $processor->next_token() ) {
if ( '#text' !== $processor->get_token_type() ) {
continue;
}
$text = $processor->get_modifiable_text();
if ( null === $text ) {
continue;
}
$processor->set_modifiable_text( str_replace( ':)', '🙂', $text ) );
}
$html = $processor->get_updated_html();If the target text may be inside script, style, or complex nested content, inspect the processor behavior on the target WP version before shipping.
Use WP_HTML_Processor when you need safe token serialization or fragment-level structure:
$processor = WP_HTML_Processor::create_fragment( $html );
$links = array();
while ( $processor->next_tag( array( 'tag_name' => 'a' ) ) ) {
$links[] = $processor->serialize_token();
}In WP 6.9, WP_HTML_Processor::serialize_token() is public. It serializes the current token in normalized form; it is not a full outerHTML extractor for arbitrary subtrees unless you explicitly walk and collect the nested tokens you need.
data-* attribute namesHTML data-* names and JS dataset properties do not map by simple dash removal in every case. For generated attributes that must line up with JS, use the core mapping helpers:
$attribute = wp_html_custom_data_attribute_name( 'myPluginSource' );
if ( null !== $attribute ) {
$processor->set_attribute( $attribute, 'content' );
}
$dataset_name = wp_js_dataset_name( 'data-my-plugin-source' );serialize_token().$html drops changes.// WRONG - regex breaks on attribute order, quotes, nesting, and malformed HTML.
$html = preg_replace( '/<a /', '<a rel="nofollow" ', $html );
// RIGHT
$p = new WP_HTML_Tag_Processor( $html );
while ( $p->next_tag( array( 'tag_name' => 'a' ) ) ) {
$p->set_attribute( 'rel', 'nofollow' );
}
$html = $p->get_updated_html();
// WRONG - escapes before the API escapes.
$p->set_attribute( 'title', esc_attr( $title ) );
// RIGHT
$p->set_attribute( 'title', $title );WP_HTML_Tag_Processor: wp-includes/html-api/class-wp-html-tag-processor.phpWP_HTML_Processor: wp-includes/html-api/class-wp-html-processor.php~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.