Bing Webmaster Tools opened one morning with a wall of “image alt missing” warnings. This is what I did about it, and what I would do differently now.
Some background: a Google core update had pushed this blog out of Discover and most of its search visibility, and traffic fell to roughly a tenth of its peak. I had been working my way back up. Alt text was one of the things I had quietly stopped maintaining along the way.
The fix I landed on is small: when an image has an empty alt attribute, fall back to the post title. Not ideal, but far better than nothing, and it scales to a site with a thousand old posts. This article covers that code, why I skipped the plugins, and how I am replacing the fallback with real descriptions as I rewrite old posts.
Originally published in Japanese on November 26, 2025. This English version was written in September 2026.
Why empty alt attributes were worth fixing at all
Alt text does three jobs at once, and it is easy to only think about one of them.
- Accessibility. A screen reader has nothing else to announce. This is the reason that matters most and the one I had been neglecting hardest.
- Search. Image search uses it, and Bing Webmaster Tools reports missing alt attributes as a site quality issue.
- Machine readers. When an AI search product or an agentic browser parses a page, the text around and inside images is part of how it reconstructs what the article is about.
In principle you set alt text while writing the post. In practice, across several WordPress sites and years of archives, gaps accumulate. Going back and hand-writing an alt attribute for every historical image was not a realistic weekend project.
So I split the problem. Automate the floor, hand-write the ceiling. Get every image to a non-empty, non-misleading alt attribute automatically, then improve the ones that matter, by hand, as part of normal rewriting work.
The plugins did more than I wanted
I started where most people start. There is no shortage of WordPress plugins that fill in alt text:
- Bulk alt text tools such as BIALTY
- Auto Image Attributes and similar attribute-filling plugins
- The image modules bundled into the big SEO plugins
- Vision-model plugins that look at the image and generate a description
They are all fine tools. My requirement was just much smaller than any of them: if alt is empty, put the post title in it. One rule.
What I actually ran into was the usual long tail. The feature I wanted sat behind a paid tier in one plugin. Another loaded a lot of machinery I had no use for. One combination broke page rendering outright and had to be backed out. Each round trip cost more time than the rule was worth.
At that point writing it myself was clearly the shorter path. Roughly thirty lines, behavior I fully understand, and nothing that changes underneath me on a plugin update.
The code I added to functions.php
Two filters. The first walks the images in post content; the second handles the featured image, which goes through a different code path in WordPress.
/*----------------------------------------------
If an image has no usable alt, fall back to the post title.
Uses WordPress's HTML API (6.2+) rather than regex or DOMDocument:
it matches attribute names exactly, survives ">" inside attribute
values, and leaves the rest of the document byte-for-byte alone.
----------------------------------------------*/
function mf_fill_empty_alt( $html, $post_title ) {
if ( '' === trim( (string) $html ) || '' === trim( (string) $post_title ) ) {
return $html;
}
if ( ! class_exists( 'WP_HTML_Tag_Processor' ) ) {
return $html; // WordPress < 6.2
}
$processor = new WP_HTML_Tag_Processor( $html );
while ( $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) {
// Respect images explicitly marked decorative.
if ( 'presentation' === $processor->get_attribute( 'role' )
|| 'true' === $processor->get_attribute( 'aria-hidden' ) ) {
continue;
}
$alt = $processor->get_attribute( 'alt' );
// null means the attribute is absent. A string that is empty or
// whitespace-only is an alt somebody left blank.
if ( is_string( $alt ) && '' !== trim( $alt ) ) {
continue;
}
// No esc_attr() here: set_attribute() escapes the value itself.
$processor->set_attribute( 'alt', $post_title );
}
return $processor->get_updated_html();
}
add_filter( 'the_content', function ( $content ) {
return mf_fill_empty_alt( $content, get_the_title( get_the_ID() ) );
}, 20 );
add_filter( 'post_thumbnail_html', function ( $html, $post_id ) {
return mf_fill_empty_alt( $html, get_the_title( $post_id ) );
}, 20, 2 );
The result was what I wanted. Images with no alt attribute went to zero, and the Bing Webmaster Tools warnings cleared. Nothing in the database changed, so any alt text I write by hand later simply wins — the filter only touches the empty ones.
Things worth knowing before you paste this in
This is a deliberately blunt instrument, and it is worth being clear about the edges.
- It filters output, it does not fix your data. The Media Library still has empty alternative text fields. If you switch themes or turn the filter off, the gaps come back. That is a feature if you think of it as a safety net, and a problem if you think of it as a migration.
- It needs WordPress 6.2 or later, which is where
WP_HTML_Tag_Processorarrived. The guard above returns the content untouched on anything older. I went through two worse versions before this one: aDOMDocumentpass that re-serialized the whole document (turning every non-ASCII character into a numeric entity and inserting a closing</source>inside<picture>), and then a regex pass that mistookdata-altforaltand broke on a>inside atitleattribute. If you find either of those in an older version of this post, do not use them — parsing HTML is the job of a parser. - An empty alt is sometimes correct. In HTML,
alt=""is the right value for a purely decorative image — it tells a screen reader to skip it. And WordPress’s own “Mark as decorative” control in the image block emits exactly that: once rendered, a deliberate decorative image and a forgotten one are indistinguishable. That is why the code above skips anything carryingrole="presentation"oraria-hidden="true"— if you use decorative images deliberately, add one of those, because otherwise this filter silently overrides your choice. - Do not wrap the value in
esc_attr().set_attribute()escapes it for you, and doing both gives youalt="T &amp; U"for a title containing an ampersand — which a screen reader reads out loud. - Repeating the post title on ten images is not good alt text. It is better than silence, and worse than a description. Treat it as a floor.
What I write by hand now
Once the automated floor was in place, the interesting work started: replacing it, image by image, on the posts that actually get read.
The rule I use is to describe what is in the frame, in a normal sentence, with the specifics a reader would want:
- A food post: “sashimi-sliced flathead arranged on a white plate”, not “cooking”.
- A travel post: “the food court in Terminal 3 at Narita Airport”, not “airport photo”.
- A technical post: “the WordPress image block sidebar with the alt text field selected”, not “screenshot”.
In the WordPress block editor this is a per-image field: select an image block and the alt text setting is in the block sidebar, and the Media Library keeps its own alternative text on the attachment itself. Both are documented on WordPress.org, linked at the end.
So the split ends up being: the filter handles new posts and the long tail automatically, and human-written alt text arrives with the rewrite, starting with the posts that get traffic. The code is not the finished state. It is the thing that stops the finished state from being urgent.
Why this sits inside rewriting rather than beside it
Alt text on its own is a small, unsatisfying SEO chore. As part of rewriting an old post it makes much more sense, because you are already re-reading the article and deciding what each image is doing there.
When I go back to an old post I look at the search intent it now serves, its internal links, the HTML it actually ships, and the images, in one pass. Automating the floor is what makes that pass affordable — I am never opening an old post to do damage control on an empty attribute, only to improve something.
References
WordPress screens and behavior change over time, so check the current admin UI alongside these.