Custom rewrite to fetch feed data

Seems like a good candidate for add_rewrite_endpoint().

add_rewrite_endpoint( 'items', EP_ROOT, 'item_id' );

That will register the endpoint /items/<item ID> and also registers the query var item_id, and eliminates the 404 status header/title as well. But the page title would be the site name and you’d probably still want to hook to pre_get_document_title to customize the title.

And your class would be simpler:

class FeedPlugin {
    public function __construct() {
        add_action( 'init', [ $this, 'add_rewrite_endpoint' ] );
        add_action( 'template_redirect', [ $this, 'addTemplate' ] );
    }

    public function add_rewrite_endpoint() {
        add_rewrite_endpoint( 'items', EP_ROOT, 'item_id' );
    }

    public function addTemplate() {
        if ( $item_id = get_query_var( 'item_id' ) ) {
            /* your code here:
            echo 'Yay, it works! Item ID: ' . $item_id;
            exit;
            */
        }
    }
}

Don’t forget to flush the rewrite rules — just visit the permalink settings page.

UPDATE

how can I specify what is valid for release_id e.g [0-9A-Z]+

So I believe you meant item_id when you said release_id?

And (at the moment) there’s no “standard”/easy way to specify the preferred RegEx pattern for an endpoint added using add_rewrite_endpoint(), so if you need a custom RegEx pattern, it’s actually better to use add_rewrite_rule(). 🙂

And here’s how you can prevent the 404 error with your original code.

I believe you know it happens because you’re querying a post/Page that doesn’t exist (pagename=items) and I also believe you added that in the rewrite rule as an identifier for the custom feed request.

And I’m not sure if you intentionally didn’t do this, but it’s a very easy way to prevent the 404 error: create a Page and set its slug to items.

That may sound silly, but it actually works. (Although you’d still need to tweak the page title.) And you could just create a custom Page template (which loads/generates/renders the feed) and assign the template to that items Page, without having to hook to template_redirect.

Alternate solution without creating a Page

  1. In the addRewriteRules(), just omit the pagename=items&:

    public function addRewriteRules()
    {
      add_rewrite_rule(
        'items/([0-9]+)/?$',
        'index.php?item_id=$matches[1]',
        'top'
      );
    }
    
  2. In the addTemplate(), check if the matched rewrite rule is the one you added via add_rewrite_rule() above:

    public function addTemplate()
    {
      global $item, $wp;
      if ( 'items/([0-9]+)/?$' === $wp->matched_rule &&
        ( $item_id = get_query_var('item_id') ) ) {
        // your code here
      }
    }
    

But once again, you’d still need to tweak the page title which defaults to the site name.