Had a client come to me last week. Their SEO guy was breathing down their neck because the shiny new FAQs page, built with the native WordPress Accordion block, was invisible to Google as a proper FAQ section. No rich snippets, no special treatment in search results. The page looked great, but to a search engine it was just a pile of divs. The reason? The default block doesn’t output the schema that crawlers need to recognize it as an FAQ.
My first thought, and maybe yours too, was to reach for a plugin. There are dozens of schema or “structured data” plugins out there. The catch: most of them want you to use their custom FAQ block. The client had just spent a week perfecting the styling of the core Accordion block, and telling them to scrap it and start over wasn’t an option. The other quick fix that crossed my mind was to use JavaScript to jam the attributes in on the front end. Don’t. It’s fragile, messy, and can wreck performance.
The right way: filter the block on the server
Since WordPress 6.2 we’ve had the WP_HTML_Tag_Processor. This class is a godsend. It lets us parse and change a block’s HTML right before it goes to the browser. No JavaScript hacks, no heavy plugin. We hook into the block’s rendering, find our specific accordion, and inject the Schema.org microdata. It’s clean, it’s efficient, and it’s how you’re meant to handle this now.
This is the exact approach I used, building on an idea from the WordPress Developer Blog. First, identify the specific accordion you want to modify. The easiest way is to give it a unique CSS class. I used is-faqs, which you can add in the block’s “Advanced” panel. Then drop this filter into your functions.php or a custom plugin:
add_filter( 'render_block_core/accordion', 'projectslug_render_accordion_faqs' );
function projectslug_render_accordion_faqs( $content ): string
{
$processor = new WP_HTML_Tag_Processor( $content );
// Bail early if there's no Accordion block with the `.is-faqs` class.
if (
! $processor->next_tag( [ 'class_name' => 'wp-block-accordion' ] )
|| ! $processor->has_class( 'is-faqs' )
) {
return $processor->get_updated_html();
}
// Add attributes to wrapping accordion block.
$processor->set_attribute( 'itemscope', true );
$processor->set_attribute( 'itemtype', 'https://schema.org/FAQPage' );
// Loop through accordion items and add attributes.
while ( $processor->next_tag( [ 'class_name' => 'wp-block-accordion-item' ] ) ) {
$processor->set_attribute( 'itemscope', true );
$processor->set_attribute( 'itemprop', 'mainEntity' );
$processor->set_attribute( 'itemtype', 'https://schema.org/Question' );
// Add attributes to the title element.
if ( $processor->next_tag( [ 'class_name' => 'wp-block-accordion-heading__toggle-title' ] ) ) {
$processor->set_attribute( 'itemprop', 'name' );
}
// Add attributes to the panel.
if ( $processor->next_tag( [ 'class_name' => 'wp-block-accordion-panel' ] ) ) {
$processor->set_attribute( 'itemscope', true );
$processor->set_attribute( 'itemprop', 'acceptedAnswer' );
$processor->set_attribute( 'itemtype', 'https://schema.org/Answer' );
// Add attribute to first paragraph.
if ( $processor->next_tag( 'p' ) ) {
$processor->set_attribute( 'itemprop', 'text' );
}
}
}
return $processor->get_updated_html();
}
So, what’s the point?
The point is to stop treating every problem as something that needs another plugin. More often than not, a few lines of well-placed code do the job better. This approach is lightweight, keeps you in control of your HTML, and doesn’t lock you into a third-party block system you can’t easily style or move away from. You solve the one problem without creating three new ones. And that was it: the SEO guy was happy, the client’s page looked the same, and Google could finally read their content.
This stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.