2026年11月13日

2026年11月13日

Extend the WordPress RSS Feed with Code: Post Types, Custom Elements

This article is a localized edition of the Japanese original. Read the Japanese original

WordPress automatically outputs an RSS 2.0 feed at https://example.com/feed/. You can change the number of items and switch between full text and excerpt in the admin screen, but changes such as "include a custom post type too," "add custom fields like a price," or "create a separate feed for one category only" need code.

This article explains, by goal, how to extend the feed with WordPress hooks (actions and filters). The code has been tested on WordPress 6.9 (as of November 2026).

Before writing code: what settings and URLs can already do

Before writing code, check whether the built-in features are enough.

What you want Built-in way
Change the number of feed items "Settings" → "Reading", "Syndication feeds show the most recent"
Switch between full text and excerpt "For each post in a feed, include" on the same screen ("Full text" or "Excerpt")
A feed for one category https://example.com/category/category-slug/feed/
A feed for one tag https://example.com/tag/tag-slug/feed/
A feed for a custom post type If the archive is enabled (has_archive), https://example.com/post-type-archive/feed/

If those are not enough, use the code below.

Where to put the code

Put the code in your child theme's functions.php or in a plugin of your own. If you put it in the parent theme's functions.php, it is lost when the theme updates. If you want the feed behavior to survive a theme switch, you can also place a PHP file in wp-content/mu-plugins/ (a Must-Use plugin, which is always loaded without being activated).

Back up before editing a production site, and test on a staging environment if you can. A PHP typo can take the whole site down.

The code below uses the prefix myprefix_ to avoid function name collisions. Change it to a name that fits your site.

Add a post type to the main feed and exclude a category

To adjust which posts appear in the main feed (/feed/), modify the main query with the pre_get_posts action. It is important to narrow the conditions so that category feeds and comment feeds are not changed too.

add_action( 'pre_get_posts', function ( $query ) {
	if ( is_admin() || ! $query->is_main_query() || ! $query->is_feed() ) {
		return;
	}
	// Leave category, search and single-post comment feeds unchanged
	if ( $query->is_archive() || $query->is_search() || $query->is_singular() || $query->is_comment_feed() ) {
		return;
	}
	// The custom feed (news-digest) described below is handled separately
	if ( 'news-digest' === $query->get( 'feed' ) ) {
		return;
	}

	// Include the custom post type book in addition to regular posts
	$query->set( 'post_type', array( 'post', 'book' ) );

	// Exclude posts in the category with ID 3
	$query->set( 'category__not_in', array( 3 ) );
} );

You can find a category's ID by opening its edit screen from "Posts" → "Categories" and reading the number after tag_ID= in the URL. book is an example; replace it with your actual post type name.

Add custom elements and the featured image to each item

When you add your own elements to RSS 2.0, the rule is to declare a namespace and use its prefix. Add the namespace to the <rss> element with the rss2_ns action, and output elements inside each <item> with the rss2_item action.

The following example adds the value of the custom field price and the featured image as media:content.

// Add namespaces to the <rss> element
add_action( 'rss2_ns', function () {
	echo 'xmlns:media="http://search.yahoo.com/mrss/"' . "\n\t";
	echo 'xmlns:myprefix="https://example.com/ns/myprefix/1.0/"' . "\n";
} );

// Add elements to each <item>
add_action( 'rss2_item', function () {
	$price = get_post_meta( get_the_ID(), 'price', true );
	if ( '' !== $price ) {
		printf( "\t\t<myprefix:price>%s</myprefix:price>\n", esc_xml( $price ) );
	}

	if ( has_post_thumbnail() ) {
		$image = wp_get_attachment_image_src( get_post_thumbnail_id(), 'large' );
		if ( $image ) {
			printf(
				"\t\t<media:content url=\"%s\" medium=\"image\" width=\"%d\" height=\"%d\" />\n",
				esc_url( $image[0] ),
				(int) $image[1],
				(int) $image[2]
			);
		}
	}
} );

Key points:

  • The namespace URL (https://example.com/ns/myprefix/1.0/) is an identifier string; it does not need to be reachable. Use a unique value based on your own domain.
  • Escape values with esc_xml() (WordPress 5.5 and later) or esc_url(). Without escaping, a value containing & or < breaks the feed as XML.
  • Adding HTML tags such as <meta> to the content does not make them feed elements. Output information you want to pass as a field as an element with rss2_item.

Add text to the feed content

To change the content that appears in the feed, use the the_content_feed filter. The following example adds a link to the original post at the end of the content.

add_filter( 'the_content_feed', function ( $content ) {
	$link = sprintf(
		'<p><a href="%s">%s</a></p>',
		esc_url( get_permalink() ),
		esc_html( 'Read this post on the site' )
	);
	return $content . $link;
} );

the_content_feed is used for the content (content:encoded) when "For each post in a feed, include" is set to "Full text." To change the excerpt (description), use the the_excerpt_rss filter.

Create your own feed: add_feed

With add_feed() you can add a new feed such as /feed/news-digest/. The following example creates a feed that outputs 20 posts from the news category. It uses WordPress's standard RSS 2.0 template (feed-rss2.php) as is, so the namespaces and elements added above also appear in this feed.

// Register the feed
add_action( 'init', function () {
	add_feed( 'news-digest', 'myprefix_render_news_digest' );
} );

// Output the feed (uses the standard RSS 2.0 template)
function myprefix_render_news_digest() {
	load_template( ABSPATH . WPINC . '/feed-rss2.php' );
}

// Choose the posts for this feed
add_action( 'pre_get_posts', function ( $query ) {
	if ( is_admin() || ! $query->is_main_query() || 'news-digest' !== $query->get( 'feed' ) ) {
		return;
	}
	$query->set( 'category_name', 'news' );
	// In feeds, set the count with posts_per_rss, not posts_per_page
	$query->set( 'posts_per_rss', 20 );
} );

After adding the code, open "Settings" → "Permalinks" and click "Save Changes" without changing anything. The URL rewrite rules added by add_feed() do not take effect until the rules are rebuilt. Even before rebuilding, you can check the output at https://example.com/?feed=news-digest.

Note that feeds use posts_per_rss, not posts_per_page, for the item count. For feeds, WordPress overwrites posts_per_page with the number from the Reading settings.

Announce where your feed is

So that feed readers and browsers can find your custom feed, output a link element in <head>.

add_action( 'wp_head', function () {
	printf(
		'<link rel="alternate" type="application/rss+xml" title="%s" href="%s" />' . "\n",
		esc_attr( get_bloginfo( 'name' ) . ' News digest' ),
		esc_url( get_feed_link( 'news-digest' ) )
	);
} );

get_feed_link( 'news-digest' ) returns a URL in the form /feed/news-digest/ or ?feed=news-digest, depending on your permalink settings.

Check that it works

  • Open the feed in a browser or from the command line and check that the added elements and posts are included.
curl -s https://example.com/feed/ | head -40
curl -s https://example.com/feed/news-digest/ | grep -c '<item>'
  • You can check that the XML is valid with the W3C Feed Validation Service.
  • Feed readers store what they fetch, so changes may not show up right away. Check by opening the URL directly. If you use a caching plugin or a CDN, clear that cache too before checking.

Common mistakes

  • The feed shows an "XML parsing error": common causes are whitespace or line breaks at the start or end of functions.php leaking into the output, or values that were not escaped. Check for line breaks after ?>.
  • The custom feed returns 404: this happens when you have not clicked "Save Changes" in Permalinks, so the URL rewrite rules have not been rebuilt. Checking whether the ?feed=news-digest form works helps narrow it down.
  • Category feeds changed too: if the pre_get_posts conditions do not exclude is_archive() and the like, every feed is affected.

Summary

  • Handle item count, full text vs. excerpt and per-category feeds with the built-in settings and URLs first
  • Change which posts appear with pre_get_posts, limited to the main feed
  • For custom elements, declare a namespace with rss2_ns and output escaped elements with rss2_item
  • Change the content with the_content_feed and the excerpt with the_excerpt_rss
  • Create custom feeds with add_feed() and rebuild the rules by saving Permalinks. Set the count with posts_per_rss

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲