Controlling and customising RSS feeds in WordPress

WordPress by default creates a whole lot of automatic feeds for you. Feeds for posts, feeds for comments, feeds for categories… just about anything you could imaginably turn into a feed it does. It doesn’t add link elements pointing to all of the feeds it creates, but nevertheless the feeds are there for anyone looking.

But what if you don’t want these magical mystery feeds? What if you want to have full control over which feeds your site has, what they contain and how they are delivered? Well, I think I have figured out how, but it was far from easy to find the information needed to make WordPress output feeds exactly the way you want to. I hope this post will make things a little easier for others looking to do the same thing.

Assuming control of WordPress feeds involves adding a few functions to your functions.php file, editing your header.php file and creating a new page template for your feed.

You can add several feeds this way, you can use different feed formats (RSS, Atom, whatever), and you can give them any name you want. The use case I’ll focus on for the rest of this article is creating a single RSS 2 feed called feed.xml that will only be available in your site’s root directory – its URL will be http://example.com/feed.xml.

Replace the automatic feed link elements

The first step is to remove the link elements referencing the feeds that WordPress magically inserts into the head element. That’s easily taken care of by putting the following into your functions.php file:

// Remove automatic links to feeds
remove_action('wp_head', 'feed_links', 2);
remove_action('wp_head', 'feed_links_extra', 3);

With the automatic links out of the way, you’ll want to add your own feed link instead. To do that, open your header.php file and insert the following in a suitable position before the </head> tag:

<link rel="alternate" type="application/rss+xml" title="My custom feed" href="/feed.xml">

Prepare your custom RSS 2 feed template

The next step is making sure the XML that your feed outputs is exactly what you want. You can create a template for this from scratch, but a convenient way is to copy the default RSS 2 template from /wp-includes/feed-rss2.php to your template directory.

Once the copy is in place you can edit it any way you want. The first thing I recommend doing is making sure it is transmitted with the correct content type. For RSS 2 it should be application/rss+xml, not text/xml as it is set to by default. Change the following:

header('Content-Type: ' . feed_content_type('rss-http') . '; charset=' . get_option('blog_charset'), true);

to this:

header('Content-Type: application/rss+xml; charset=' . get_option('blog_charset'), true);

If you’re reading this you likely know what character encoding you’re using, so you might as well make that static as well:

header('Content-Type: application/rss+xml; charset=utf-8', true);

As for cleaning up the rest of the feed template I’ll leave that to you. I personally think it contains quite a bit of unnecessary stuff that can be removed.

Disable automatic feeds

Next you need to disable the magical automatic feeds. The process is similar to removing the automatic feed links. Add this to functions.php:

// Disable automatic feeds
remove_action( 'do_feed_rdf', 'do_feed_rdf', 10, 1 );
remove_action( 'do_feed_rss', 'do_feed_rss', 10, 1 );
remove_action( 'do_feed_rss2', 'do_feed_rss2', 10, 1 );
remove_action( 'do_feed_atom', 'do_feed_atom', 10, 1 );

That should take care of the default feeds that show up att http://example.com/feed/, http://example.com/feed/rss2/ etc. At this point WordPress displays an error message saying “not a valid feed template” for those URLs, but later on I’ll show you how to get rid of that as well and show a 404 page instead.

Create your custom feed and remove default rewrite rules

Time for the stuff that I had a really hard time finding complete information about. Before diving into the code, here’s what I want to do:

  • Create a feed that uses the feed-rss2.php template in my theme directory
  • Make the feed available at http://example.com/feed.xml
  • Remove the default rewrite rules that assume feeds for comments, categories, tags, etc. are available

To create the custom feed you can use the do_feed hook to call the template you created earlier:

function create_custom_feed() {
    load_template( TEMPLATEPATH . '/feed-rss2.php');
}
add_action('do_feed_custom_feed', 'create_custom_feed', 10, 1);

The important part here is what’s after do_feed_ in the add_action call, since that will become the name of the feed (“custom_feed” in this case). The create_custom_feed function just calls in the template to create the feed’s markup.

At this stage you can access the feed by adding its name to the querystring, i.e. http://example.com/?feed=custom_feed. To make it available as /feed.xml you need to create a rewrite rule. This is a good time to also find all existing rewrite rules that are feed related and remove them.

To do that, use this function to change the array that holds all rewrite rules:

function customise_feed_rules($rules) {
    // Remove all feed related rules
    $filtered_rules = array_filter($rules, function($rule) {
        return !preg_match("/feed/i", $rule);
    });
    // Add the rule(s) for your custom feed(s)
    $new_rules = array(
        'feed\.xml$' => 'index.php?feed=custom_feed'
    );
    return $new_rules + $filtered_rules;
}

The rewrite_rules_array filter is used to run the function. You also need to make it run when WordPress is initialised, and you need to use the flush_rules function to save the new rewrite rules (both of these are described on the WP Rewrite page in the WordPress Codex). To do this I’ve wrapped the call in another function which is run via the add_action hook. I also let the same function create the custom feed:

function add_custom_feed() {
    global $wp_rewrite;
    add_action('do_feed_custom_feed', 'create_custom_feed', 10, 1);
    add_filter('rewrite_rules_array','customise_feed_rules');
    $wp_rewrite->flush_rules();
}

add_action('init', 'add_custom_feed');

I’ve read in some places that the flush_rules function is expensive and shouldn’t be called on init this way. It shouldn’t be necessary since the rewrite rules are supposed to be stored in the database. However it seems like it needs to be called since if I remove it (after running it once), the rules stop working. Furthermore when I test the performance with ApacheBench any performance hit is so small that it’s hard to say if it’s caused by the flush_rules call or something else.

If anyone knows differently and can point me to a description of how to make sure flush_rules is only called once, please drop me a line.

Putting it all together

After that step-by-step explanation, here’s all of the necessary code in one place.

header.php

<link rel="alternate" type="application/rss+xml" title="My custom feed" href="/feed.xml">

feed-rss2.php

header('Content-Type: application/rss+xml; charset=utf-8', true);

functions.php

// Disable automatic feeds
remove_action( 'do_feed_rdf', 'do_feed_rdf', 10, 1 );
remove_action( 'do_feed_rss', 'do_feed_rss', 10, 1 );
remove_action( 'do_feed_rss2', 'do_feed_rss2', 10, 1 );
remove_action( 'do_feed_atom', 'do_feed_atom', 10, 1 );

// Remove automatic links to feeds
remove_action('wp_head', 'feed_links', 2);
remove_action('wp_head', 'feed_links_extra', 3);

// Create custom feed from template
function create_custom_feed() {
    load_template( TEMPLATEPATH . '/feed-rss2.php');
}

// Replace default feed rewrite rules
function customise_feed_rules($rules) {
    // Remove all feed related rules
    $filtered_rules = array_filter($rules, function($rule) {
        return !preg_match("/feed/i", $rule);
    });
    // Add the rule(s) for your custom feed(s)
    $new_rules = array(
        'feed\.xml$' => 'index.php?feed=custom_feed'
    );
    return $new_rules + $filtered_rules;
}

// Add the custom feed and update rewrite rules
function add_custom_feed() {
    global $wp_rewrite;
    add_action('do_feed_custom_feed', 'create_custom_feed', 10, 1);
    add_filter('rewrite_rules_array','customise_feed_rules');
    $wp_rewrite->flush_rules();
}

add_action('init', 'add_custom_feed');

I hope it’s of use to someone else.

Posted on March 24, 2011 in WordPress

Comments are disabled for this post (read why), but if you have spotted an error or have additional info that you think should be in this post, feel free to contact me.