A feed item without its image can feel like a postcard with the picture torn off. The words still arrive, but the visual cue a subscriber remembers—the diagram, finished project, person, or place—never makes the trip.

Adding that image is not difficult. The thoughtful part is deciding how it should travel. Some readers display HTML inside the item content; newsletter and syndication systems may look for a Media RSS element instead. WordPress gives us hooks for both, and they solve different contracts.

Look at the feed before changing it

  1. Open https://example.com/feed/ using your actual site hostname.

  2. Find a recent published post that definitely has a featured image.

  3. Inspect the raw XML—not only the browser’s styled preview—for an <img tag inside item content and for media:content metadata.

  4. Check Settings → Reading → For each post in a feed, include to learn whether the site is intended to publish full text or summaries.

  5. Identify the real consumer: a feed reader, newsletter provider, social automation tool, or custom application.

  6. Temporarily disable any feed, SEO, newsletter, CDN, or optimization feature already injecting images before adding another implementation.

Choose the representation your consumer understands

HTML inside the feed content

Prepending an ordinary linked <img> to the feed content is the most direct option when the reader renders item HTML. It can work for RSS and Atom because WordPress exposes the the_content_feed filter after normal content processing. The excerpt has its own the_excerpt_rss filter.

Media RSS metadata

A media:content element exposes the image URL and dimensions as structured RSS 2.0 metadata. It helps only when the downstream service understands the Media RSS namespace. It does not guarantee that a reader will place the image above the article text.

Both, but only after a consumer test

Some publishers use HTML for people and Media RSS for machines. That is reasonable when tested consumers need both. It can also make an importer display two images, so do not enable both simply because more markup feels more complete.

Install behavior as a plugin, not a theme promise

Feed behavior belongs to the site. If it lives only in functions.php, switching themes silently removes it. Create a normal single-file plugin through your deployment workflow, or place it in wp-content/mu-plugins/ when your team deliberately uses must-use plugins. Take a recoverable backup and stage the change first.

site-feed-featured-images.phpphp
<?php
/*
 * Plugin Name: Site Feed Featured Images
 * Description: Prepends each post's featured image to feed content and excerpts.
 * Version: 1.0.0
 */
 
defined( 'ABSPATH' ) || exit;
 
function lynxbee_prepend_featured_image_to_feed( $content ) {
    if ( ! is_feed() ) {
        return $content;
    }
 
    $post_id = get_the_ID();
 
    if ( ! $post_id || ! has_post_thumbnail( $post_id ) ) {
        return $content;
    }
 
    $image = get_the_post_thumbnail(
        $post_id,
        'large',
        array(
            'class' => 'feed-featured-image__img',
            'style' => 'display:block;max-width:100%;height:auto;',
        )
    );
    $permalink = get_permalink( $post_id );
 
    if ( '' === $image || ! $permalink ) {
        return $content;
    }
 
    $figure = sprintf(
        '<p class="feed-featured-image"><a href="%1$s">%2$s</a></p>',
        esc_url( $permalink ),
        $image
    );
 
    return $figure . $content;
}
 
add_filter( 'the_content_feed', 'lynxbee_prepend_featured_image_to_feed', 20 );
add_filter( 'the_excerpt_rss', 'lynxbee_prepend_featured_image_to_feed', 20 );

Prepend responsive WordPress thumbnail HTML to both full-content and excerpt feed paths.

What the HTML plugin is doing for the reader

  • The direct-access guard exits if WordPress has not loaded, avoiding accidental standalone execution.

  • is_feed() confines the callback to feed requests even if another component invokes a related filter unexpectedly.

  • get_the_ID() uses the current feed loop item; has_post_thumbnail() avoids empty wrappers.

  • get_the_post_thumbnail() returns an <img> with WordPress-generated source, dimensions, classes, alternate text, and responsive attributes when available.

  • The registered large size avoids sending the original upload by default. Choose a site-specific registered size based on downstream display and bandwidth.

  • esc_url() protects the permalink attribute. The image HTML is generated by WordPress and is intentionally not escaped into visible text.

  • Both filters return the original content on every failure path; forgetting the return can blank feed items.

  • Priority 20 runs after default-priority callbacks, but conflicts must still be tested against the actual plugin stack.

Why both content filters are present

WordPress and feed templates distinguish content from excerpts. A site configured for summaries—or a consumer favoring the description field—can otherwise miss an image added only through the_content_feed. Hooking both paths improves coverage, but the same feed item may contain separate description and content fields.

  • Test the exact feed type WordPress advertises, usually RSS 2.0 at /feed/.

  • Test an Atom endpoint separately if the site publishes or advertises one.

  • Inspect what the target newsletter service imports, not merely what a browser preview shows.

  • If one consumer renders both fields and duplicates the image, configure that consumer or use only the filter it consumes.

  • Do not use the deprecated the_content_rss hook; WordPress directs modern implementations to the_content_feed.

Add Media RSS only when the integration asks for it

The following RSS 2.0 extension can live in the same plugin file after the HTML implementation, or in a separate site plugin. Use it when the receiving product documents Media RSS support.

site-feed-media-rss.phpphp
<?php
/*
 * Add Media RSS metadata for featured images to RSS 2.0 items.
 */
 
function lynxbee_add_media_rss_namespace() {
    echo ' xmlns:media="http://search.yahoo.com/mrss/"';
}
add_action( 'rss2_ns', 'lynxbee_add_media_rss_namespace' );
 
function lynxbee_add_featured_image_media_node() {
    $attachment_id = get_post_thumbnail_id();
 
    if ( ! $attachment_id ) {
        return;
    }
 
    $source = wp_get_attachment_image_src( $attachment_id, 'full' );
 
    if ( ! $source ) {
        return;
    }
 
    $mime_type = get_post_mime_type( $attachment_id );
    $type_attr = $mime_type
        ? sprintf( ' type="%s"', esc_attr( $mime_type ) )
        : '';
 
    printf(
        "<media:content url="%1$s" medium="image"%2$s width="%3$d" height="%4$d" />\n",
        esc_url( $source[0] ),
        $type_attr,
        absint( $source[1] ),
        absint( $source[2] )
    );
}
add_action( 'rss2_item', 'lynxbee_add_featured_image_media_node' );

Declare the Media RSS namespace and add one featured-image media node to each eligible RSS 2.0 item.

Why valid XML needs both hooks

  • rss2_ns runs on the root <rss> element, where the media namespace is declared once.

  • rss2_item runs near the end of every RSS 2.0 <item>, where item metadata belongs.

  • get_post_thumbnail_id() returns the attachment selected as the featured image.

  • wp_get_attachment_image_src() supplies URL, width, and height for the requested size.

  • get_post_mime_type() adds an optional media type such as image/jpeg when WordPress knows it.

  • esc_url(), esc_attr(), and absint() constrain dynamic XML attribute values.

  • This extension targets RSS 2.0. Atom requires Atom-appropriate design and hooks rather than copying namespace output blindly.

Deploy without gambling the whole feed

  1. Create a staging copy with representative posts: different formats, missing alt text, no featured image, a large original, and an image already present in the article.

  2. Install and activate only the chosen plugin path.

  3. Request the main feed plus category, tag, author, and custom-post-type feeds subscribers actually use.

  4. Validate the XML and inspect several complete items.

  5. Import the staging feed into the real reader or newsletter preview.

  6. Confirm image URL accessibility from outside the administrator session.

  7. Deploy through version control or the host’s normal release process.

  8. Invalidate only necessary application or CDN caches, then verify the public feed again.

Inspect the response, not browser decoration

Terminalbash
curl --fail-with-body --silent --show-error \
  https://example.com/feed/ \
  --output /tmp/example-feed.xml
xmllint --noout /tmp/example-feed.xml
rg -n '<img|media:content' /tmp/example-feed.xml
42:...<img width="1024" height="576" src="https://example.com/..."...
58:...<media:content url="https://example.com/..." medium="image"...

What this check can and cannot prove

  • --fail-with-body makes HTTP failures nonzero while retaining diagnostic content; silent/show-error removes progress noise without hiding errors.

  • xmllint --noout proves well-formed XML, not semantic compatibility with every RSS reader.

  • rg reveals whether markup exists and helps spot two images in one field.

  • The command writes a temporary local copy that may contain sensitive feed content; protect and remove it according to workstation policy.

  • The URL and output are illustrative. No external WordPress feed was downloaded during this rewrite because no authorized test site was provided.

Terminalbash
wp cache flush

Risk level: caution. Review the command before running it.

Why cache flushing is not the first fix

  • The command requires WP-CLI in the correct WordPress installation and may need the application user’s permissions.

  • It flushes the object cache for the site or shared cache context; the performance impact can extend beyond feeds.

  • A CDN, page cache, reverse proxy, or newsletter provider can retain its own copy afterward.

  • Prefer targeted invalidation when supported, and verify response headers before blaming WordPress.

  • This command was documentation-reviewed and was not executed against a WordPress environment.

When the image still does not arrive

  • No image exists in raw XML: confirm the post has a featured image, the plugin is active, the request is a feed, and another callback is not replacing content later.

  • XML contains escaped image markup: the wrong layer escaped HTML, or the custom feed template is not wrapping content as expected.

  • The browser shows it but email does not: the importer may strip HTML, prefer another field, require Media RSS, proxy images, or retain an older fetch.

  • Two images appear: article content or another extension already includes one; disable one producer instead of hiding the symptom with CSS.

  • Image is enormous: use a registered intermediate size and confirm derivatives exist; avoid full for HTML without a measured reason.

  • Image is broken outside the site: inspect absolute URLs, HTTPS, hotlink protection, authentication, firewall rules, CDN transformations, and media migrations.

  • Only some posts fail: check missing attachment files, attachment metadata, unsupported formats, and posts without a featured image.

  • Feed becomes invalid: look for PHP warnings before the XML declaration, illegal characters, malformed markup, duplicate namespaces, and unescaped attributes.

  • Changes seem delayed: inspect origin and CDN cache headers, reader refresh cadence, newsletter snapshots, and object-cache state.

Performance and privacy travel with the image

  • Use a generated size appropriate for inbox and feed widths; a multi-megabyte original punishes every subscriber.

  • Retain explicit width and height so capable clients can reserve space.

  • Expect some email systems to proxy images and some privacy settings to block remote loading.

  • Do not encode private media URLs, signed administrator links, tracking secrets, or draft assets into a public feed.

  • Decide intentionally whether image requests include analytics parameters; subscribers deserve the same privacy discipline as website visitors.

  • Keep the canonical post link around the image so constrained readers still offer a path to the accessible page.

A five-minute editorial check prevents technical disappointment

Before publishing, preview the featured image at a narrow width, read its alternative text aloud, and ask whether it still makes sense without the page’s surrounding layout. A crop that is beautiful in a wide hero can become an unrecognizable strip in an inbox.

The code carries the file. Editorial care carries the meaning. When both survive the feed, subscribers receive something closer to the post you meant to send—not just whatever markup happened to escape the website.

Continue building the WordPress publication path

WordPress references